about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/README5
-rw-r--r--nixos/README.md86
-rw-r--r--nixos/doc/manual/administration/service-mgmt.chapter.md30
-rw-r--r--nixos/doc/manual/contributing-to-this-manual.chapter.md91
-rw-r--r--nixos/doc/manual/default.nix6
-rw-r--r--nixos/doc/manual/manpages/README.md57
-rw-r--r--nixos/doc/manual/manpages/nixos-option.889
-rw-r--r--nixos/doc/manual/manpages/nixos-rebuild.8452
-rw-r--r--nixos/doc/manual/release-notes/rl-2311.section.md35
-rw-r--r--nixos/lib/make-squashfs.nix4
-rwxr-xr-xnixos/lib/test-driver/test_driver/__init__.py8
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix57
-rw-r--r--nixos/modules/image/amend-repart-definitions.py4
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-build-vms.8 (renamed from nixos/doc/manual/manpages/nixos-build-vms.8)0
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-enter.8 (renamed from nixos/doc/manual/manpages/nixos-enter.8)0
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-generate-config.8 (renamed from nixos/doc/manual/manpages/nixos-generate-config.8)0
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-install.8 (renamed from nixos/doc/manual/manpages/nixos-install.8)0
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-version.8 (renamed from nixos/doc/manual/manpages/nixos-version.8)0
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/tools.nix11
-rw-r--r--nixos/modules/misc/documentation.nix2
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/environment.nix7
-rw-r--r--nixos/modules/programs/htop.nix3
-rw-r--r--nixos/modules/security/wrappers/default.nix10
-rw-r--r--nixos/modules/security/wrappers/wrapper.c109
-rw-r--r--nixos/modules/security/wrappers/wrapper.nix4
-rw-r--r--nixos/modules/services/audio/goxlr-utility.nix48
-rw-r--r--nixos/modules/services/continuous-integration/gitea-actions-runner.nix26
-rw-r--r--nixos/modules/services/databases/influxdb2.nix450
-rw-r--r--nixos/modules/services/editors/emacs.nix17
-rw-r--r--nixos/modules/services/games/openarena.nix6
-rw-r--r--nixos/modules/services/games/quake3-server.nix10
-rw-r--r--nixos/modules/services/hardware/hddfancontrol.nix67
-rw-r--r--nixos/modules/services/logging/graylog.nix4
-rw-r--r--nixos/modules/services/matrix/conduit.nix10
-rw-r--r--nixos/modules/services/matrix/synapse.nix87
-rw-r--r--nixos/modules/services/misc/zoneminder.nix2
-rw-r--r--nixos/modules/services/networking/haproxy.nix17
-rw-r--r--nixos/modules/services/networking/headscale.nix2
-rw-r--r--nixos/modules/services/networking/jool.nix222
-rw-r--r--nixos/modules/services/networking/tailscale.nix2
-rw-r--r--nixos/modules/services/networking/twingate.nix2
-rw-r--r--nixos/modules/services/security/kanidm.nix14
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix2
-rw-r--r--nixos/modules/services/security/opensnitch.nix14
-rw-r--r--nixos/modules/services/tracing/tempo.nix14
-rw-r--r--nixos/modules/services/video/mediamtx.nix53
-rw-r--r--nixos/modules/services/web-apps/grocy.nix8
-rw-r--r--nixos/modules/services/web-apps/invidious.nix64
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix4
-rw-r--r--nixos/modules/services/web-apps/netbox.nix98
-rw-r--r--nixos/modules/services/web-apps/photoprism.nix2
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix54
-rw-r--r--nixos/modules/services/web-servers/rustus.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix9
-rw-r--r--nixos/modules/services/x11/picom.nix6
-rw-r--r--nixos/modules/services/x11/window-managers/dwm.nix2
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl58
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl59
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix49
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix21
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/lxd-agent.nix91
-rw-r--r--nixos/release.nix2
-rw-r--r--nixos/tests/all-tests.nix6
-rw-r--r--nixos/tests/budgie.nix6
-rw-r--r--nixos/tests/caddy.nix22
-rw-r--r--nixos/tests/fcitx5/config11
-rw-r--r--nixos/tests/fcitx5/default.nix47
-rw-r--r--nixos/tests/fcitx5/profile27
-rw-r--r--nixos/tests/hddfancontrol.nix44
-rw-r--r--nixos/tests/influxdb2.nix225
-rw-r--r--nixos/tests/jool.nix250
-rw-r--r--nixos/tests/matrix/synapse.nix15
-rw-r--r--nixos/tests/mediamtx.nix57
-rw-r--r--nixos/tests/nixops/default.nix9
-rw-r--r--nixos/tests/opensnitch.nix62
-rw-r--r--nixos/tests/os-prober.nix1
-rw-r--r--nixos/tests/pantheon.nix3
-rw-r--r--nixos/tests/prometheus-exporters.nix2
-rw-r--r--nixos/tests/switch-test.nix162
-rw-r--r--nixos/tests/systemd-boot.nix10
-rw-r--r--nixos/tests/virtualbox.nix2
-rw-r--r--nixos/tests/web-apps/mastodon/remote-postgresql.nix2
-rw-r--r--nixos/tests/web-apps/netbox-upgrade.nix85
-rw-r--r--nixos/tests/web-servers/agate.nix2
-rw-r--r--nixos/tests/wrappers.nix11
90 files changed, 2661 insertions, 1103 deletions
diff --git a/nixos/README b/nixos/README
deleted file mode 100644
index ce4dd1988d2d5..0000000000000
--- a/nixos/README
+++ /dev/null
@@ -1,5 +0,0 @@
-*** NixOS ***
-
-NixOS is a Linux distribution based on the purely functional package
-management system Nix.  More information can be found at
-https://nixos.org/nixos and in the manual in doc/manual.
diff --git a/nixos/README.md b/nixos/README.md
new file mode 100644
index 0000000000000..b3cd9d234fa61
--- /dev/null
+++ b/nixos/README.md
@@ -0,0 +1,86 @@
+# NixOS
+
+NixOS is a Linux distribution based on the purely functional package
+management system Nix.  More information can be found at
+https://nixos.org/nixos and in the manual in doc/manual.
+
+## Testing changes
+
+You can add new module to your NixOS configuration file (usually it’s `/etc/nixos/configuration.nix`). And do `sudo nixos-rebuild test -I nixpkgs=<path to your local nixpkgs folder> --fast`.
+
+## Reviewing contributions
+
+When changing the bootloader installation process, extra care must be taken. Grub installations cannot be rolled back, hence changes may break people’s installations forever. For any non-trivial change to the bootloader please file a PR asking for review, especially from \@edolstra.
+
+### Module updates
+
+Module updates are submissions changing modules in some ways. These often contains changes to the options or introduce new options.
+
+Reviewing process:
+
+- Ensure that the module maintainers are notified.
+  - [CODEOWNERS](https://help.github.com/articles/about-codeowners/) will make GitHub notify users based on the submitted changes, but it can happen that it misses some of the package maintainers.
+- Ensure that the module tests, if any, are succeeding.
+- Ensure that the introduced options are correct.
+  - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
+  - Description, default and example should be provided.
+- Ensure that option changes are backward compatible.
+  - `mkRenamedOptionModuleWith` provides a way to make option changes backward compatible.
+- Ensure that removed options are declared with `mkRemovedOptionModule`
+- Ensure that changes that are not backward compatible are mentioned in release notes.
+- Ensure that documentations affected by the change is updated.
+
+Sample template for a module update review is provided below.
+
+```markdown
+##### Reviewed points
+
+- [ ] changes are backward compatible
+- [ ] removed options are declared with `mkRemovedOptionModule`
+- [ ] changes that are not backward compatible are documented in release notes
+- [ ] module tests succeed on ARCHITECTURE
+- [ ] options types are appropriate
+- [ ] options description is set
+- [ ] options example is provided
+- [ ] documentation affected by the changes is updated
+
+##### Possible improvements
+
+##### Comments
+```
+
+### New modules
+
+New modules submissions introduce a new module to NixOS.
+
+Reviewing process:
+
+- Ensure that the module tests, if any, are succeeding.
+- Ensure that the introduced options are correct.
+  - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
+  - Description, default and example should be provided.
+- Ensure that module `meta` field is present
+  - Maintainers should be declared in `meta.maintainers`.
+  - Module documentation should be declared with `meta.doc`.
+- Ensure that the module respect other modules functionality.
+  - For example, enabling a module should not open firewall ports by default.
+
+Sample template for a new module review is provided below.
+
+```markdown
+##### Reviewed points
+
+- [ ] module path fits the guidelines
+- [ ] module tests succeed on ARCHITECTURE
+- [ ] options have appropriate types
+- [ ] options have default
+- [ ] options have example
+- [ ] options have descriptions
+- [ ] No unneeded package is added to environment.systemPackages
+- [ ] meta.maintainers is set
+- [ ] module documentation is declared in meta.doc
+
+##### Possible improvements
+
+##### Comments
+```
diff --git a/nixos/doc/manual/administration/service-mgmt.chapter.md b/nixos/doc/manual/administration/service-mgmt.chapter.md
index 674c737416805..bc9bdbe3708bc 100644
--- a/nixos/doc/manual/administration/service-mgmt.chapter.md
+++ b/nixos/doc/manual/administration/service-mgmt.chapter.md
@@ -118,3 +118,33 @@ the symlink, and this path is in `/nix/store/.../lib/systemd/user/`.
 Hence [garbage collection](#sec-nix-gc) will remove that file and you
 will wind up with a broken symlink in your systemd configuration, which
 in turn will not make the service / timer start on login.
+
+## Template units {#sect-nixos-systemd-template-units}
+
+systemd supports templated units where a base unit can be started multiple
+times with a different parameter. The syntax to accomplish this is
+`service-name@instance-name.service`. Units get the instance name passed to
+them (see `systemd.unit(5)`). NixOS has support for these kinds of units and
+for template-specific overrides. A service needs to be defined twice, once
+for the base unit and once for the instance. All instances must include
+`overrideStrategy = "asDropin"` for the change detection to work. This
+example illustrates this:
+```nix
+{
+  systemd.services = {
+    "base-unit@".serviceConfig = {
+      ExecStart = "...";
+      User = "...";
+    };
+    "base-unit@instance-a" = {
+      overrideStrategy = "asDropin"; # needed for templates to work
+      wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance
+    };
+    "base-unit@instance-b" = {
+      overrideStrategy = "asDropin"; # needed for templates to work
+      wantedBy = [ "multi-user.target" ]; # causes NixOS to manage the instance
+      serviceConfig.User = "root"; # also override something for this specific instance
+    };
+  };
+}
+```
diff --git a/nixos/doc/manual/contributing-to-this-manual.chapter.md b/nixos/doc/manual/contributing-to-this-manual.chapter.md
index 0a4f5403fdeb2..6245280e30f06 100644
--- a/nixos/doc/manual/contributing-to-this-manual.chapter.md
+++ b/nixos/doc/manual/contributing-to-this-manual.chapter.md
@@ -7,6 +7,7 @@ You can quickly check your edits with the following:
 
 ```ShellSession
 $ cd /path/to/nixpkgs
+$ $EDITOR doc/nixos/manual/... # edit the manual
 $ nix-build nixos/release.nix -A manual.x86_64-linux
 ```
 
@@ -14,24 +15,96 @@ If the build succeeds, the manual will be in `./result/share/doc/nixos/index.htm
 
 There's also [a convenient development daemon](https://nixos.org/manual/nixpkgs/unstable/#sec-contributing-devmode).
 
-**Contributing to the man pages**
+The above instructions don't deal with the appendix of available `configuration.nix` options, and the manual pages related to NixOS. These are built, and written in a different location and in a different format, as explained in the next sections.
 
-The man pages are written in [DocBook] which is XML.
+## Contributing to the `configuration.nix` options documentation {#sec-contributing-options}
 
-To see what your edits look like:
+The documentation for all the different `configuration.nix` options is automatically generated by reading the `description`s of all the NixOS options defined at `nixos/modules/`. If you want to improve such `description`, find it in the `nixos/modules/` directory, and edit it and open a pull request.
+
+To see how your changes render on the web, run again:
+
+```ShellSession
+$ nix-build nixos/release.nix -A manual.x86_64-linux
+```
+
+And you'll see the changes to the appendix in the path `result/share/doc/nixos/options.html`.
+
+You can also build only the `configuration.nix(5)` manual page, via:
 
 ```ShellSession
 $ cd /path/to/nixpkgs
-$ nix-build nixos/release.nix -A manpages.x86_64-linux
+$ nix-build nixos/release.nix -A nixos-configuration-reference-manpage.x86_64-linux
 ```
 
-You can then read the man page you edited by running
+And observe the result via:
 
 ```ShellSession
-$ man --manpath=result/share/man nixos-rebuild # Replace nixos-rebuild with the command whose manual you edited
+$ man --local-file result/share/man/man5/configuration.nix.5
+```
+
+If you're on a different architecture that's supported by NixOS (check file `nixos/release.nix` on Nixpkgs' repository) then replace `x86_64-linux` with the architecture. `nix-build` will complain otherwise, but should also tell you which architecture you have + the supported ones.
+
+## Contributing to `nixos-*` tools' manpages {#sec-contributing-nixos-tools}
+
+The manual pages for the tools available in the installation image can be found in Nixpkgs by running (e.g for `nixos-rebuild`):
+
+```ShellSession
+$ git ls | grep nixos-rebuild.8
+```
+
+Man pages are written in [`mdoc(7)` format](https://mandoc.bsd.lv/man/mdoc.7.html) and should be portable between mandoc and groff for rendering (except for minor differences, notably different spacing rules.)
+
+For a preview, run `man --local-file path/to/file.8`.
+
+Being written in `mdoc`, these manpages use semantic markup. This following subsections provides a guideline on where to apply which semantic elements.
+
+### Command lines and arguments {#ssec-contributing-nixos-tools-cli-and-args}
+
+In any manpage, commands, flags and arguments to the *current* executable should be marked according to their semantics. Commands, flags and arguments passed to *other* executables should not be marked like this and should instead be considered as code examples and marked with `Ql`.
+
+- Use `Fl` to mark flag arguments, `Ar` for their arguments.
+- Repeating arguments should be marked by adding an ellipsis (spelled with periods, `...`).
+- Use `Cm` to mark literal string arguments, e.g. the `boot` command argument passed to `nixos-rebuild`.
+- Optional flags or arguments should be marked with `Op`. This includes optional repeating arguments.
+- Required flags or arguments should not be marked.
+- Mutually exclusive groups of arguments should be enclosed in curly brackets, preferably created with `Bro`/`Brc` blocks.
+
+When an argument is used in an example it should be marked up with `Ar` again to differentiate it from a constant. For example, a command with a `--host name` option that calls ssh to retrieve the host's local time would signify this thusly:
+```
+This will run
+.Ic ssh Ar name Ic time
+to retrieve the remote time.
 ```
 
-If you're on a different architecture that's supported by NixOS (check nixos/release.nix) then replace `x86_64-linux` with the architecture.
-`nix-build` will complain otherwise, but should also tell you which architecture you have + the supported ones.
+### Paths, NixOS options, environment variables {#ssec-contributing-nixos-tools-options-and-environment}
+
+Constant paths should be marked with `Pa`, NixOS options with `Va`, and environment variables with `Ev`.
+
+Generated paths, e.g. `result/bin/run-hostname-vm` (where `hostname` is a variable or arguments) should be marked as `Ql` inline literals with their variable components marked appropriately.
+
+ - When `hostname` refers to an argument, it becomes `.Ql result/bin/run- Ns Ar hostname Ns -vm`
+ - When `hostname` refers to a variable, it becomes `.Ql result/bin/run- Ns Va hostname Ns -vm`
+
+### Code examples and other commands {#ssec-contributing-nixos-tools-code-examples}
 
-[DocBook]: https://en.wikipedia.org/wiki/DocBook
+In free text names and complete invocations of other commands (e.g. `ssh` or `tar -xvf src.tar`) should be marked with `Ic`, fragments of command lines should be marked with `Ql`.
+
+Larger code blocks or those that cannot be shown inline should use indented literal display block markup for their contents, i.e.
+
+```
+.Bd -literal -offset indent
+...
+.Ed
+```
+
+Contents of code blocks may be marked up further, e.g. if they refer to arguments that will be substituted into them:
+
+```
+.Bd -literal -offset indent
+{
+  config.networking.hostname = "\c
+.Ar hostname Ns \c
+";
+}
+.Ed
+```
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index 902dee7018010..a368b16201f8f 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -184,8 +184,8 @@ in rec {
     '';
 
 
-  # Generate the NixOS manpages.
-  manpages = runCommand "nixos-manpages"
+  # Generate the `man configuration.nix` package
+  nixos-configuration-reference-manpage = runCommand "nixos-configuration-reference-manpage"
     { nativeBuildInputs = [
         buildPackages.installShellFiles
         buildPackages.nixos-render-docs
@@ -194,8 +194,6 @@ in rec {
     }
     ''
       # Generate manpages.
-      mkdir -p $out/share/man/man8
-      installManPage ${./manpages}/*
       mkdir -p $out/share/man/man5
       nixos-render-docs -j $NIX_BUILD_CORES options manpage \
         --revision ${lib.escapeShellArg revision} \
diff --git a/nixos/doc/manual/manpages/README.md b/nixos/doc/manual/manpages/README.md
deleted file mode 100644
index 05cb83902c742..0000000000000
--- a/nixos/doc/manual/manpages/README.md
+++ /dev/null
@@ -1,57 +0,0 @@
-# NixOS manpages
-
-This is the collection of NixOS manpages, excluding `configuration.nix(5)`.
-
-Man pages are written in [`mdoc(7)` format](https://mandoc.bsd.lv/man/mdoc.7.html) and should be portable between mandoc and groff for rendering (though minor differences may occur, mandoc and groff seem to have slightly different spacing rules.)
-
-For previewing edited files, you can just run `man -l path/to/file.8` and you will see it rendered.
-
-Being written in `mdoc` these manpages use semantic markup. This file provides a guideline on where to apply which of the semantic elements of `mdoc`.
-
-### Command lines and arguments
-
-In any manpage, commands, flags and arguments to the *current* executable should be marked according to their semantics. Commands, flags and arguments passed to *other* executables should not be marked like this and should instead be considered as code examples and marked with `Ql`.
-
- - Use `Fl` to mark flag arguments, `Ar` for their arguments.
- - Repeating arguments should be marked by adding ellipses (`...`).
- - Use `Cm` to mark literal string arguments, e.g. the `boot` command argument passed to `nixos-rebuild`.
- - Optional flags or arguments should be marked with `Op`. This includes optional repeating arguments.
- - Required flags or arguments should not be marked.
- - Mutually exclusive groups of arguments should be enclosed in curly brackets, preferably created with `Bro`/`Brc` blocks.
-
-When an argument is used in an example it should be marked up with `Ar` again to differentiate it from a constant. For example, a command with a `--host name` flag that calls ssh to retrieve the host's local time would signify this thusly:
-```
-This will run
-.Ic ssh Ar name Ic time
-to retrieve the remote time.
-```
-
-### Paths, NixOS options, environment variables
-
-Constant paths should be marked with `Pa`, NixOS options with `Va`, and environment variables with `Ev`.
-
-Generated paths, e.g. `result/bin/run-hostname-vm` (where `hostname` is a variable or arguments) should be marked as `Ql` inline literals with their variable components marked appropriately.
-
- - Taking `hostname` from an argument become `.Ql result/bin/run- Ns Ar hostname Ns -vm`
- - Taking `hostname` from a variable otherwise defined becomes `.Ql result/bin/run- Ns Va hostname Ns -vm`
-
-### Code examples and other commands
-
-In free text names and complete invocations of other commands (e.g. `ssh` or `tar -xvf src.tar`) should be marked with `Ic`, fragments of command lines should be marked with `Ql`.
-
-Larger code blocks or those that cannot be shown inline should use indented literal display block markup for their contents, i.e.
-```
-.Bd -literal -offset indent
-...
-.Ed
-```
-Contents of code blocks may be marked up further, e.g. if they refer to arguments that will be substituted into them:
-```
-.Bd -literal -offset indent
-{
-  options.hostname = "\c
-.Ar hostname Ns \c
-";
-}
-.Ed
-```
diff --git a/nixos/doc/manual/manpages/nixos-option.8 b/nixos/doc/manual/manpages/nixos-option.8
deleted file mode 100644
index 28438b03580b1..0000000000000
--- a/nixos/doc/manual/manpages/nixos-option.8
+++ /dev/null
@@ -1,89 +0,0 @@
-.Dd January 1, 1980
-.Dt nixos-option 8
-.Os
-.Sh NAME
-.Nm nixos-option
-.Nd inspect a NixOS configuration
-.
-.
-.
-.Sh SYNOPSIS
-.Nm
-.Op Fl r | -recursive
-.Op Fl I Ar path
-.Ar option.name
-.
-.
-.
-.Sh DESCRIPTION
-This command evaluates the configuration specified in
-.Pa /etc/nixos/configuration.nix
-and returns the properties of the option name given as argument.
-.
-.Pp
-When the option name is not an option, the command prints the list of attributes
-contained in the attribute set.
-.
-.
-.
-.Sh OPTIONS
-.Bl -tag -width indent
-.It Fl r , -recursive
-Print all the values at or below the specified path recursively.
-.
-.It Fl I Ar path
-This option is passed to the underlying
-.Xr nix-instantiate 1
-invocation.
-.El
-.
-.
-.
-.Sh ENVIRONMENT
-.Bl -tag -width indent
-.It Ev NIXOS_CONFIG
-Path to the main NixOS configuration module. Defaults to
-.Pa /etc/nixos/configuration.nix Ns
-\&.
-.El
-.
-.
-.
-.Sh EXAMPLES
-Investigate option values:
-.Bd -literal -offset indent
-$ nixos-option boot.loader
-This attribute set contains:
-generationsDir
-grub
-initScript
-
-$ nixos-option boot.loader.grub.enable
-Value:
-true
-
-Default:
-true
-
-Description:
-Whether to enable the GNU GRUB boot loader.
-
-Declared by:
-  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
-
-Defined by:
-  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
-.Ed
-.
-.
-.
-.Sh SEE ALSO
-.Xr configuration.nix 5
-.
-.
-.
-.Sh AUTHORS
-.An -nosplit
-.An Nicolas Pierron
-and
-.An the Nixpkgs/NixOS contributors
diff --git a/nixos/doc/manual/manpages/nixos-rebuild.8 b/nixos/doc/manual/manpages/nixos-rebuild.8
deleted file mode 100644
index 64bbbee411d7f..0000000000000
--- a/nixos/doc/manual/manpages/nixos-rebuild.8
+++ /dev/null
@@ -1,452 +0,0 @@
-.Dd January 1, 1980
-.Dt nixos-rebuild 8
-.Os
-.Sh NAME
-.Nm nixos-rebuild
-.Nd reconfigure a NixOS machine
-.
-.
-.
-.Sh SYNOPSIS
-.Nm
-.Bro
-.Cm switch | boot | test | build | dry-build | dry-activate | edit | build-vm | build-vm-with-bootloader
-.Brc
-.br
-.Op Fl -upgrade | -upgrade-all
-.Op Fl -install-bootloader
-.Op Fl -no-build-nix
-.Op Fl -fast
-.Op Fl -rollback
-.Op Fl -builders Ar builder-spec
-.br
-.Op Fl -flake Ar flake-uri
-.Op Fl -no-flake
-.Op Fl -override-input Ar input-name flake-uri
-.br
-.Op Fl -profile-name | p Ar name
-.Op Fl -specialisation | c Ar name
-.br
-.Op Fl -build-host Va host
-.Op Fl -target-host Va host
-.Op Fl -use-remote-sudo
-.br
-.Op Fl -show-trace
-.Op Fl I Va NIX_PATH
-.Op Fl -verbose | v
-.Op Fl -impure
-.Op Fl -max-jobs | j Va number
-.Op Fl -keep-failed | K
-.Op Fl -keep-going | k
-.
-.
-.
-.Sh DESCRIPTION
-This command updates the system so that it corresponds to the
-configuration specified in
-.Pa /etc/nixos/configuration.nix
-or
-.Pa /etc/nixos/flake.nix Ns
-\&. Thus, every time you modify the configuration or any other NixOS module, you
-must run
-.Nm
-to make the changes take effect. It builds the new system in
-.Pa /nix/store Ns
-, runs its activation script, and stop and (re)starts any system services if
-needed. Please note that user services need to be started manually as they
-aren't detected by the activation script at the moment.
-.
-.Pp
-This command has one required argument, which specifies the desired
-operation. It must be one of the following:
-.Bl -tag -width indent
-.It Cm switch
-Build and activate the new configuration, and make it the boot default. That
-is, the configuration is added to the GRUB boot menu as the default
-menu entry, so that subsequent reboots will boot the system into the new
-configuration. Previous configurations activated with
-.Ic nixos-rebuild switch
-or
-.Ic nixos-rebuild boot
-remain available in the GRUB menu.
-.Pp
-Note that if you are using specializations, running just
-.Ic nixos-rebuild switch
-will switch you back to the unspecialized, base system \(em in that case, you
-might want to use this instead:
-.Bd -literal -offset indent
-$ nixos-rebuild switch --specialisation your-specialisation-name
-.Ed
-.Pp
-This command will build all specialisations and make them bootable just
-like regular
-.Ic nixos-rebuild switch
-does \(em the only thing different is that it will switch to given
-specialisation instead of the base system; it can be also used to switch from
-the base system into a specialised one, or to switch between specialisations.
-.
-.It Cm boot
-Build the new configuration and make it the boot default (as with
-.Ic nixos-rebuild switch Ns
-), but do not activate it. That is, the system continues to run the previous
-configuration until the next reboot.
-.
-.It Cm test
-Build and activate the new configuration, but do not add it to the GRUB
-boot menu. Thus, if you reboot the system (or if it crashes), you will
-automatically revert to the default configuration (i.e. the
-configuration resulting from the last call to
-.Ic nixos-rebuild switch
-or
-.Ic nixos-rebuild boot Ns
-).
-.Pp
-Note that if you are using specialisations, running just
-.Ic nixos-rebuild test
-will activate the unspecialised, base system \(em in that case, you might want
-to use this instead:
-.Bd -literal -offset indent
-$ nixos-rebuild test --specialisation your-specialisation-name
-.Ed
-.Pp
-This command can be also used to switch from the base system into a
-specialised one, or to switch between specialisations.
-.
-.It Cm build
-Build the new configuration, but neither activate it nor add it to the
-GRUB boot menu. It leaves a symlink named
-.Pa result
-in the current directory, which points to the output of the top-level
-.Dq system
-derivation. This is essentially the same as doing
-.Bd -literal -offset indent
-$ nix-build /path/to/nixpkgs/nixos -A system
-.Ed
-.Pp
-Note that you do not need to be root to run
-.Ic nixos-rebuild build Ns
-\&.
-.
-.It Cm dry-build
-Show what store paths would be built or downloaded by any of the
-operations above, but otherwise do nothing.
-.
-.It Cm dry-activate
-Build the new configuration, but instead of activating it, show what
-changes would be performed by the activation (i.e. by
-.Ic nixos-rebuild test Ns
-). For instance, this command will print which systemd units would be restarted.
-The list of changes is not guaranteed to be complete.
-.
-.It Cm edit
-Opens
-.Pa configuration.nix
-in the default editor.
-.
-.It Cm build-vm
-Build a script that starts a NixOS virtual machine with the desired
-configuration. It leaves a symlink
-.Pa result
-in the current directory that points (under
-.Ql result/bin/run\- Ns Va hostname Ns \-vm Ns
-)
-at the script that starts the VM. Thus, to test a NixOS configuration in
-a virtual machine, you should do the following:
-.Bd -literal -offset indent
-$ nixos-rebuild build-vm
-$ ./result/bin/run-*-vm
-.Ed
-.Pp
-The VM is implemented using the
-.Ql qemu
-package. For best performance, you should load the
-.Ql kvm-intel
-or
-.Ql kvm-amd
-kernel modules to get hardware virtualisation.
-.Pp
-The VM mounts the Nix store of the host through the 9P file system. The
-host Nix store is read-only, so Nix commands that modify the Nix store
-will not work in the VM. This includes commands such as
-.Nm Ns
-; to change the VM’s configuration, you must halt the VM and re-run the commands
-above.
-.Pp
-The VM has its own ext3 root file system, which is automatically created when
-the VM is first started, and is persistent across reboots of the VM. It is
-stored in
-.Ql ./ Ns Va hostname Ns .qcow2 Ns
-\&.
-.\" The entire file system hierarchy of the host is available in
-.\" the VM under
-.\" .Pa /hostfs Ns
-.\" .
-.
-.It Cm build-vm-with-bootloader
-Like
-.Cm build-vm Ns
-, but boots using the regular boot loader of your configuration (e.g. GRUB 1 or
-2), rather than booting directly into the kernel and initial ramdisk of the
-system. This allows you to test whether the boot loader works correctly. \
-However, it does not guarantee that your NixOS configuration will boot
-successfully on the host hardware (i.e., after running
-.Ic nixos-rebuild switch Ns
-), because the hardware and boot loader configuration in the VM are different.
-The boot loader is installed on an automatically generated virtual disk
-containing a
-.Pa /boot
-partition.
-.El
-.
-.
-.
-.Sh OPTIONS
-.Bl -tag -width indent
-.It Fl -upgrade , -upgrade-all
-Update the root user's channel named
-.Ql nixos
-before rebuilding the system.
-.Pp
-In addition to the
-.Ql nixos
-channel, the root user's channels which have a file named
-.Ql .update-on-nixos-rebuild
-in their base directory will also be updated.
-.Pp
-Passing
-.Fl -upgrade-all
-updates all of the root user's channels.
-.
-.It Fl -install-bootloader
-Causes the boot loader to be (re)installed on the device specified by the
-relevant configuration options.
-.
-.It Fl -no-build-nix
-Normally,
-.Nm
-first builds the
-.Ql nixUnstable
-attribute in Nixpkgs, and uses the resulting instance of the Nix package manager
-to build the new system configuration. This is necessary if the NixOS modules
-use features not provided by the currently installed version of Nix. This option
-disables building a new Nix.
-.
-.It Fl -fast
-Equivalent to
-.Fl -no-build-nix Ns
-\&. This option is useful if you call
-.Nm
-frequently (e.g. if you’re hacking on a NixOS module).
-.
-.It Fl -rollback
-Instead of building a new configuration as specified by
-.Pa /etc/nixos/configuration.nix Ns
-, roll back to the previous configuration. (The previous configuration is
-defined as the one before the “current” generation of the Nix profile
-.Pa /nix/var/nix/profiles/system Ns
-\&.)
-.
-.It Fl -builders Ar builder-spec
-Allow ad-hoc remote builders for building the new system. This requires
-the user executing
-.Nm
-(usually root) to be configured as a trusted user in the Nix daemon. This can be
-achieved by using the
-.Va nix.settings.trusted-users
-NixOS option. Examples values for that option are described in the
-.Dq Remote builds
-chapter in the Nix manual, (i.e.
-.Ql --builders \(dqssh://bigbrother x86_64-linux\(dq Ns
-). By specifying an empty string existing builders specified in
-.Pa /etc/nix/machines
-can be ignored:
-.Ql --builders \(dq\(dq
-for example when they are not reachable due to network connectivity.
-.
-.It Fl -profile-name Ar name , Fl p Ar name
-Instead of using the Nix profile
-.Pa /nix/var/nix/profiles/system
-to keep track of the current and previous system configurations, use
-.Pa /nix/var/nix/profiles/system-profiles/ Ns Va name Ns
-\&. When you use GRUB 2, for every system profile created with this flag, NixOS
-will create a submenu named
-.Dq NixOS - Profile Va name
-in GRUB’s boot menu, containing the current and previous configurations of this profile.
-.Pp
-For instance, if you want to test a configuration file named
-.Pa test.nix
-without affecting the default system profile, you would do:
-.Bd -literal -offset indent
-$ nixos-rebuild switch -p test -I nixos-config=./test.nix
-.Ed
-.Pp
-The new configuration will appear in the GRUB 2 submenu
-.Dq NixOS - Profile 'test' Ns
-\&.
-.
-.It Fl -specialisation Ar name , Fl c Ar name
-Activates given specialisation; when not specified, switching and testing
-will activate the base, unspecialised system.
-.
-.It Fl -build-host Ar host
-Instead of building the new configuration locally, use the specified host
-to perform the build. The host needs to be accessible with
-.Ic ssh Ns ,
-and must be able to perform Nix builds. If the option
-.Fl -target-host
-is not set, the build will be copied back to the local machine when done.
-.Pp
-Note that, if
-.Fl -no-build-nix
-is not specified, Nix will be built both locally and remotely. This is because
-the configuration will always be evaluated locally even though the building
-might be performed remotely.
-.Pp
-You can include a remote user name in the host name
-.Ns ( Va user@host Ns
-). You can also set ssh options by defining the
-.Ev NIX_SSHOPTS
-environment variable.
-.
-.It Fl -target-host Ar host
-Specifies the NixOS target host. By setting this to something other than an
-empty string, the system activation will happen on the remote host instead of
-the local machine. The remote host needs to be accessible over
-.Ic ssh Ns ,
-and for the commands
-.Cm switch Ns
-,
-.Cm boot
-and
-.Cm test
-you need root access.
-.Pp
-If
-.Fl -build-host
-is not explicitly specified or empty, building will take place locally.
-.Pp
-You can include a remote user name in the host name
-.Ns ( Va user@host Ns
-). You can also set ssh options by defining the
-.Ev NIX_SSHOPTS
-environment variable.
-.Pp
-Note that
-.Nm
-honors the
-.Va nixpkgs.crossSystem
-setting of the given configuration but disregards the true architecture of the
-target host. Hence the
-.Va nixpkgs.crossSystem
-setting has to match the target platform or else activation will fail.
-.
-.It Fl -use-substitutes
-When set, nixos-rebuild will add
-.Fl -use-substitutes
-to each invocation of nix-copy-closure. This will only affect the behavior of
-nixos-rebuild if
-.Fl -target-host
-or
-.Fl -build-host
-is also set. This is useful when the target-host connection to cache.nixos.org
-is faster than the connection between hosts.
-.
-.It Fl -use-remote-sudo
-When set, nixos-rebuild prefixes remote commands that run on the
-.Fl -build-host
-and
-.Fl -target-host
-systems with
-.Ic sudo Ns
-\&. Setting this option allows deploying as a non-root user.
-.
-.It Fl -flake Va flake-uri Ns Op Va #name
-Build the NixOS system from the specified flake. It defaults to the directory
-containing the target of the symlink
-.Pa /etc/nixos/flake.nix Ns
-, if it exists. The flake must contain an output named
-.Ql nixosConfigurations. Ns Va name Ns
-\&. If
-.Va name
-is omitted, it default to the current host name.
-.
-.It Fl -no-flake
-Do not imply
-.Fl -flake
-if
-.Pa /etc/nixos/flake.nix
-exists. With this option, it is possible to build non-flake NixOS configurations
-even if the current NixOS systems uses flakes.
-.El
-.Pp
-In addition,
-.Nm
-accepts various Nix-related flags, including
-.Fl -max-jobs Ns ,
-.Fl j Ns ,
-.Fl I Ns ,
-.Fl -show-trace Ns ,
-.Fl -keep-failed Ns ,
-.Fl -keep-going Ns ,
-.Fl -impure Ns ,
-.Fl -verbose Ns , and
-.Fl v Ns
-\&. See the Nix manual for details.
-.
-.
-.
-.Sh ENVIRONMENT
-.Bl -tag -width indent
-.It Ev NIXOS_CONFIG
-Path to the main NixOS configuration module. Defaults to
-.Pa /etc/nixos/configuration.nix Ns
-\&.
-.
-.It Ev NIX_PATH
-A colon-separated list of directories used to look up Nix expressions enclosed
-in angle brackets (e.g. <nixpkgs>). Example:
-.Bd -literal -offset indent
-nixpkgs=./my-nixpkgs
-.Ed
-.
-.It Ev NIX_SSHOPTS
-Additional options to be passed to
-.Ic ssh
-on the command line.
-.El
-.
-.
-.
-.Sh FILES
-.Bl -tag -width indent
-.It Pa /etc/nixos/flake.nix
-If this file exists, then
-.Nm
-will use it as if the
-.Fl -flake
-option was given. This file may be a symlink to a
-.Pa flake.nix
-in an actual flake; thus
-.Pa /etc/nixos
-need not be a flake.
-.
-.It Pa /run/current-system
-A symlink to the currently active system configuration in the Nix store.
-.
-.It Pa /nix/var/nix/profiles/system
-The Nix profile that contains the current and previous system
-configurations. Used to generate the GRUB boot menu.
-.El
-.
-.
-.
-.Sh BUGS
-This command should be renamed to something more descriptive.
-.
-.
-.
-.Sh AUTHORS
-.An -nosplit
-.An Eelco Dolstra
-and
-.An the Nixpkgs/NixOS contributors
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md
index 2f286d13f2b6a..623576ce4ff28 100644
--- a/nixos/doc/manual/release-notes/rl-2311.section.md
+++ b/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -20,6 +20,8 @@
 
 - [mautrix-whatsapp](https://docs.mau.fi/bridges/go/whatsapp/index.html) A Matrix-WhatsApp puppeting bridge
 
+- [hddfancontrol](https://github.com/desbma/hddfancontrol), a service to regulate fan speeds based on hard drive temperature. Available as [services.hddfancontrol](#opt-services.hddfancontrol.enable).
+
 - [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
 
 - [Typesense](https://github.com/typesense/typesense), a fast, typo-tolerant search engine for building delightful search experiences. Available as [services.typesense](#opt-services.typesense.enable).
@@ -30,6 +32,8 @@
 
 - [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable).
 
+- [Jool](https://nicmx.github.io/Jool/en/index.html), an Open Source implementation of IPv4/IPv6 translation on Linux. Available as [networking.jool.enable](#opt-networking.jool.enable).
+
 - [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
 
 - [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
@@ -68,6 +72,13 @@
 
 - The `services.ananicy.extraRules` option now has the type of `listOf attrs` instead of `string`.
 
+- The `matrix-synapse` package & module have undergone some significant internal changes, for most setups no intervention is needed, though:
+  - The option [`services.matrix-synapse.package`](#opt-services.matrix-synapse.package) is now read-only. For modifying the package, use an overlay which modifies `matrix-synapse-unwrapped` instead. More on that below.
+  - The `enableSystemd` & `enableRedis` arguments have been removed and `matrix-synapse` has been renamed to `matrix-synapse-unwrapped`. Also, several optional dependencies (such as `psycopg2` or `authlib`) have been removed.
+  - These optional dependencies are automatically added via a wrapper (`pkgs.matrix-synapse.override { extras = ["redis"]; }` for `hiredis` & `txredisapi` for instance) if the relevant config section is declared in `services.matrix-synapse.settings`. For instance, if `services.matrix-synapse.settings.redis.enabled` is set to `true`, `"redis"` will be automatically added to the `extras` list of `pkgs.matrix-synapse`.
+  - A list of all extras (and the extras enabled by default) can be found at the [option's reference for `services.matrix-synapse.extras`](#opt-services.matrix-synapse.extras).
+  - In some cases (e.g. for running synapse workers) it was necessary to re-use the `PYTHONPATH` of `matrix-synapse.service`'s environment to have all plugins available. This isn't necessary anymore, instead `config.services.matrix-synapse.package` can be used as it points to the wrapper with properly configured `extras` and also all plugins defined via [`services.matrix-synapse.plugins`](#opt-services.matrix-synapse.plugins) available. This is also the reason for why the option is read-only now, it's supposed to be set by the module only.
+
 - `etcd` has been updated to 3.5, you will want to read the [3.3 to 3.4](https://etcd.io/docs/v3.5/upgrades/upgrade_3_4/) and [3.4 to 3.5](https://etcd.io/docs/v3.5/upgrades/upgrade_3_5/) upgrade guides
 
 - `consul` has been updated to `1.16.0`. See the [release note](https://github.com/hashicorp/consul/releases/tag/v1.16.0) for more details. Once a new Consul version has started and upgraded its data directory, it generally cannot be downgraded to the previous version.
@@ -136,11 +147,17 @@
 
 - `pharo` has been updated to latest stable (PharoVM 10.0.5), which is compatible with the latest stable and oldstable images (Pharo 10 and 11). The VM in question is the 64bit Spur. The 32bit version has been dropped due to lack of maintenance. The Cog VM has been deleted because it is severily outdated. Finally, the `pharo-launcher` package has been deleted because it was not compatible with the newer VM, and due to lack of maintenance.
 
+- Emacs mainline version 29 was introduced. This new version includes many major additions, most notably `tree-sitter` support (enabled by default) and the pgtk variant (useful for Wayland users), which is available under the attribute `emacs29-pgtk`.
+
+- Emacs macport version 29 was introduced.
+
+- The `html-proofer` package has been updated from major version 3 to major version 5, which includes [breaking changes](https://github.com/gjtorikian/html-proofer/blob/v5.0.8/UPGRADING.md).
+
 ## Other Notable Changes {#sec-release-23.11-notable-changes}
 
 - The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration.
 
-- GNOME module no longer forces Qt applications to use Adwaita style since it was buggy and is no longer maintained upstream. If you still want it, you can add the following options to your configuration but it will probably be eventually removed:
+- GNOME, Pantheon, Cinnamon module no longer forces Qt applications to use Adwaita style since it was buggy and is no longer maintained upstream (specifically, Cinnamon now defaults to the gtk2 style instead, following the default in Linux Mint). If you still want it, you can add the following options to your configuration but it will probably be eventually removed:
 
   ```nix
   qt = {
@@ -170,6 +187,8 @@
 
 - `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets.
 
+- The application firewall `opensnitch` now uses the process monitor method eBPF as default as recommended by upstream. The method can be changed with the setting [services.opensnitch.settings.ProcMonitorMethod](#opt-services.opensnitch.settings.ProcMonitorMethod).
+
 - The module [services.ankisyncd](#opt-services.ankisyncd.package) has been switched to [anki-sync-server-rs](https://github.com/ankicommunity/anki-sync-server-rs) from the old python version, which was difficult to update, had not been updated in a while, and did not support recent versions of anki.
 Unfortunately all servers supporting new clients (newer version of anki-sync-server, anki's built in sync server and this new rust package) do not support the older sync protocol that was used in the old server, so such old clients will also need updating and in particular the anki package in nixpkgs is also being updated in this release.
 The module update takes care of the new config syntax and the data itself (user login and cards) are compatible, so users of the module will be able to just log in again after updating both client and server without any extra action.
@@ -184,6 +203,8 @@ The module update takes care of the new config syntax and the data itself (user
 
 - `programs.gnupg.agent.pinentryFlavor` is now set in `/etc/gnupg/gpg-agent.conf`, and will no longer take precedence over a `pinentry-program` set in `~/.gnupg/gpg-agent.conf`.
 
+- `services.influxdb2` now supports doing an automatic initial setup and provisioning of users, organizations, buckets and authentication tokens, see [#249502](https://github.com/NixOS/nixpkgs/pull/249502) for more details.
+
 - `wrapHelm` now exposes `passthru.pluginsDir` which can be passed to `helmfile`. For convenience, a top-level package `helmfile-wrapped` has been added, which inherits `passthru.pluginsDir` from `kubernetes-helm-wrapped`. See [#217768](https://github.com/NixOS/nixpkgs/issues/217768) for details.
 
 - `boot.initrd.network.udhcp.enable` allows control over dhcp during stage 1 regardless of what `networking.useDHCP` is set to.
@@ -194,6 +215,18 @@ The module update takes care of the new config syntax and the data itself (user
 
 - The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and similar lines in package derivations using the default `unpackPhase` is deprecated as it requires `unpackPhase` to always produce a directory named "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`, or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead.
 
+- The `django` alias in the python package set was upgraded to Django 4.x.
+  Applications that consume Django should always pin their python environment
+  to a compatible major version, so they can move at their own pace.
+
+  ```nix
+  python = python3.override {
+    packageOverrides = self: super: {
+      django = super.django_3;
+    };
+  };
+  ```
+
 - The `qemu-vm.nix` module by default now identifies block devices via
   persistent names available in `/dev/disk/by-*`. Because the rootDevice is
   identfied by its filesystem label, it needs to be formatted before the VM is
diff --git a/nixos/lib/make-squashfs.nix b/nixos/lib/make-squashfs.nix
index d1260a48f2294..b7c7078b73b1b 100644
--- a/nixos/lib/make-squashfs.nix
+++ b/nixos/lib/make-squashfs.nix
@@ -1,4 +1,4 @@
-{ stdenv, squashfsTools, closureInfo
+{ lib, stdenv, squashfsTools, closureInfo
 
 , # The root directory of the squashfs filesystem is filled with the
   # closures of the Nix store paths listed here.
@@ -22,11 +22,13 @@ stdenv.mkDerivation {
       # for nix-store --load-db.
       cp $closureInfo/registration nix-path-registration
 
+    '' + lib.optionalString stdenv.buildPlatform.is32bit ''
       # 64 cores on i686 does not work
       # fails with FATAL ERROR: mangle2:: xz compress failed with error code 5
       if ((NIX_BUILD_CORES > 48)); then
         NIX_BUILD_CORES=48
       fi
+    '' + ''
 
       # Generate the squashfs image.
       mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
diff --git a/nixos/lib/test-driver/test_driver/__init__.py b/nixos/lib/test-driver/test_driver/__init__.py
index db7e0ed33a892..c90e3d9e1cdb6 100755
--- a/nixos/lib/test-driver/test_driver/__init__.py
+++ b/nixos/lib/test-driver/test_driver/__init__.py
@@ -106,7 +106,13 @@ def main() -> None:
         args.keep_vm_state,
     ) as driver:
         if args.interactive:
-            ptpython.repl.embed(driver.test_symbols(), {})
+            history_dir = os.getcwd()
+            history_path = os.path.join(history_dir, ".nixos-test-history")
+            ptpython.repl.embed(
+                driver.test_symbols(),
+                {},
+                history_filename=history_path,
+            )
         else:
             tic = time.time()
             driver.run_tests()
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index b72f4be9b57a9..3d52c08888eae 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -6,6 +6,7 @@ let
   im = config.i18n.inputMethod;
   cfg = im.fcitx5;
   fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; };
+  settingsFormat = pkgs.formats.ini { };
 in
 {
   options = {
@@ -40,6 +41,44 @@ in
         '';
         description = lib.mdDoc "Quick phrase files.";
       };
+      settings = {
+        globalOptions = lib.mkOption {
+          type = lib.types.submodule {
+            freeformType = settingsFormat.type;
+          };
+          default = { };
+          description = lib.mdDoc ''
+            The global options in `config` file in ini format.
+          '';
+        };
+        inputMethod = lib.mkOption {
+          type = lib.types.submodule {
+            freeformType = settingsFormat.type;
+          };
+          default = { };
+          description = lib.mdDoc ''
+            The input method configure in `profile` file in ini format.
+          '';
+        };
+        addons = lib.mkOption {
+          type = with lib.types; (attrsOf anything);
+          default = { };
+          description = lib.mdDoc ''
+            The addon configures in `conf` folder in ini format with global sections.
+            Each item is written to the corresponding file.
+          '';
+          example = literalExpression "{ pinyin.globalSection.EmojiEnabled = \"True\"; }";
+        };
+      };
+      ignoreUserConfig = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Ignore the user configures. **Warning**: When this is enabled, the
+          user config files are totally ignored and the user dict can't be saved
+          and loaded.
+        '';
+      };
     };
   };
 
@@ -61,12 +100,30 @@ in
         (name: value: lib.nameValuePair ("share/fcitx5/data/quickphrase.d/${name}.mb") value)
         cfg.quickPhraseFiles))
     ];
+    environment.etc =
+      let
+        optionalFile = p: f: v: lib.optionalAttrs (v != { }) {
+          "xdg/fcitx5/${p}".text = f v;
+        };
+      in
+      lib.attrsets.mergeAttrsList [
+        (optionalFile "config" (lib.generators.toINI { }) cfg.settings.globalOptions)
+        (optionalFile "profile" (lib.generators.toINI { }) cfg.settings.inputMethod)
+        (lib.concatMapAttrs
+          (name: value: optionalFile
+            "conf/${name}.conf"
+            (lib.generators.toINIWithGlobalSection { })
+            value)
+          cfg.settings.addons)
+      ];
 
     environment.variables = {
       GTK_IM_MODULE = "fcitx";
       QT_IM_MODULE = "fcitx";
       XMODIFIERS = "@im=fcitx";
       QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
+    } // lib.optionalAttrs cfg.ignoreUserConfig {
+      SKIP_FCITX_USER_PATH = "1";
     };
   };
 }
diff --git a/nixos/modules/image/amend-repart-definitions.py b/nixos/modules/image/amend-repart-definitions.py
index 52f10303eb5ea..fa9b1544ae852 100644
--- a/nixos/modules/image/amend-repart-definitions.py
+++ b/nixos/modules/image/amend-repart-definitions.py
@@ -53,7 +53,7 @@ def add_closure_to_definition(
 
             source = Path(line.strip())
             target = str(source.relative_to("/nix/store/"))
-            target = f":{target}" if strip_nix_store_prefix else ""
+            target = f":/{target}" if strip_nix_store_prefix else ""
 
             copy_files_lines.append(f"CopyFiles={source}{target}\n")
 
@@ -102,7 +102,7 @@ def main() -> None:
         add_contents_to_definition(definition, contents)
 
         closure = config.get("closure")
-        strip_nix_store_prefix = config.get("stripStorePaths")
+        strip_nix_store_prefix = config.get("stripNixStorePrefix")
         add_closure_to_definition(definition, closure, strip_nix_store_prefix)
 
     print(target_dir.absolute())
diff --git a/nixos/doc/manual/manpages/nixos-build-vms.8 b/nixos/modules/installer/tools/manpages/nixos-build-vms.8
index 6a8f2c42eddfa..6a8f2c42eddfa 100644
--- a/nixos/doc/manual/manpages/nixos-build-vms.8
+++ b/nixos/modules/installer/tools/manpages/nixos-build-vms.8
diff --git a/nixos/doc/manual/manpages/nixos-enter.8 b/nixos/modules/installer/tools/manpages/nixos-enter.8
index 646f92199d62f..646f92199d62f 100644
--- a/nixos/doc/manual/manpages/nixos-enter.8
+++ b/nixos/modules/installer/tools/manpages/nixos-enter.8
diff --git a/nixos/doc/manual/manpages/nixos-generate-config.8 b/nixos/modules/installer/tools/manpages/nixos-generate-config.8
index 1b95599e156ae..1b95599e156ae 100644
--- a/nixos/doc/manual/manpages/nixos-generate-config.8
+++ b/nixos/modules/installer/tools/manpages/nixos-generate-config.8
diff --git a/nixos/doc/manual/manpages/nixos-install.8 b/nixos/modules/installer/tools/manpages/nixos-install.8
index c6c8ed15224d3..c6c8ed15224d3 100644
--- a/nixos/doc/manual/manpages/nixos-install.8
+++ b/nixos/modules/installer/tools/manpages/nixos-install.8
diff --git a/nixos/doc/manual/manpages/nixos-version.8 b/nixos/modules/installer/tools/manpages/nixos-version.8
index f661611599fb0..f661611599fb0 100644
--- a/nixos/doc/manual/manpages/nixos-version.8
+++ b/nixos/modules/installer/tools/manpages/nixos-version.8
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 582334a5aeaf5..10c37a46fdac1 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/ny9r65799s7xhp605bc2753sjvzkxrrs-nix-2.15.1";
-  i686-linux = "/nix/store/ck55dz5klc7szi8rx9ghhm8gi2b5q5bw-nix-2.15.1";
-  aarch64-linux = "/nix/store/cl0a02vr28913dgw98hrm45a4baqr3z1-nix-2.15.1";
-  x86_64-darwin = "/nix/store/wq228jdbz16pp2lnxf32n8dv27pw53p8-nix-2.15.1";
-  aarch64-darwin = "/nix/store/x11cpsjg4q236msfz5scc325pfp9xy64-nix-2.15.1";
+  x86_64-linux = "/nix/store/3wqasl97rjiza3vd7fxjnvli2w9l30mk-nix-2.17.0";
+  i686-linux = "/nix/store/z360xswxfx55pmm1fng3hw748rbs0kkj-nix-2.17.0";
+  aarch64-linux = "/nix/store/9670sxa916xmv8n1kqs7cdvmnsrhrdjv-nix-2.17.0";
+  x86_64-darwin = "/nix/store/2rdbky9j8hc3mbgl6pnda4hkjllyfwnn-nix-2.17.0";
+  aarch64-darwin = "/nix/store/jl9qma14fb4zk9lq1k0syw2k9qm2gqjw-nix-2.17.0";
 }
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 4dce4f998052c..6564b583464a2 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -9,12 +9,19 @@ let
   makeProg = args: pkgs.substituteAll (args // {
     dir = "bin";
     isExecutable = true;
+    nativeBuildInputs = [
+      pkgs.installShellFiles
+    ];
+    postInstall = ''
+      installManPage ${args.manPage}
+    '';
   });
 
   nixos-build-vms = makeProg {
     name = "nixos-build-vms";
     src = ./nixos-build-vms/nixos-build-vms.sh;
     inherit (pkgs) runtimeShell;
+    manPage = ./manpages/nixos-build-vms.8;
   };
 
   nixos-install = makeProg {
@@ -27,6 +34,7 @@ let
       nixos-enter
       pkgs.util-linuxMinimal
     ];
+    manPage = ./manpages/nixos-install.8;
   };
 
   nixos-rebuild = pkgs.nixos-rebuild.override { nix = config.nix.package.out; };
@@ -40,6 +48,7 @@ let
     btrfs = "${pkgs.btrfs-progs}/bin/btrfs";
     inherit (config.system.nixos-generate-config) configuration desktopConfiguration;
     xserverEnabled = config.services.xserver.enable;
+    manPage = ./manpages/nixos-generate-config.8;
   };
 
   inherit (pkgs) nixos-option;
@@ -57,6 +66,7 @@ let
     } // optionalAttrs (config.system.configurationRevision != null) {
       configurationRevision = config.system.configurationRevision;
     });
+    manPage = ./manpages/nixos-version.8;
   };
 
   nixos-enter = makeProg {
@@ -66,6 +76,7 @@ let
     path = makeBinPath [
       pkgs.util-linuxMinimal
     ];
+    manPage = ./manpages/nixos-enter.8;
   };
 
 in
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 820450e3ce2a8..46462c5abd435 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -346,7 +346,7 @@ in
       system.build.manual = manual;
 
       environment.systemPackages = []
-        ++ optional cfg.man.enable manual.manpages
+        ++ optional cfg.man.enable manual.nixos-configuration-reference-manpage
         ++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ];
     })
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2c9e42f8e1741..3812aa14760dd 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -319,6 +319,7 @@
   ./services/audio/botamusique.nix
   ./services/audio/gmediarender.nix
   ./services/audio/gonic.nix
+  ./services/audio/goxlr-utility.nix
   ./services/audio/hqplayerd.nix
   ./services/audio/icecast.nix
   ./services/audio/jack.nix
@@ -504,6 +505,7 @@
   ./services/hardware/fancontrol.nix
   ./services/hardware/freefall.nix
   ./services/hardware/fwupd.nix
+  ./services/hardware/hddfancontrol.nix
   ./services/hardware/illum.nix
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
@@ -928,6 +930,7 @@
   ./services/networking/jibri/default.nix
   ./services/networking/jicofo.nix
   ./services/networking/jitsi-videobridge.nix
+  ./services/networking/jool.nix
   ./services/networking/kea.nix
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
@@ -1465,6 +1468,7 @@
   ./virtualisation/lxc.nix
   ./virtualisation/lxcfs.nix
   ./virtualisation/lxd.nix
+  ./virtualisation/lxd-agent.nix
   ./virtualisation/multipass.nix
   ./virtualisation/nixos-containers.nix
   ./virtualisation/oci-containers.nix
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index a448727be778f..3fbda153e0b44 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -51,13 +51,6 @@ in
 
     environment.extraInit =
       ''
-         unset ASPELL_CONF
-         for i in ${concatStringsSep " " (reverseList cfg.profiles)} ; do
-           if [ -d "$i/lib/aspell" ]; then
-             export ASPELL_CONF="dict-dir $i/lib/aspell"
-           fi
-         done
-
          export NIX_USER_PROFILE_DIR="/nix/var/nix/profiles/per-user/$USER"
          export NIX_PROFILES="${concatStringsSep " " (reverseList cfg.profiles)}"
       '';
diff --git a/nixos/modules/programs/htop.nix b/nixos/modules/programs/htop.nix
index 2682ced490ca3..777ea709836e6 100644
--- a/nixos/modules/programs/htop.nix
+++ b/nixos/modules/programs/htop.nix
@@ -9,7 +9,8 @@ let
   fmt = value:
     if isList value then concatStringsSep " " (map fmt value) else
     if isString value then value else
-    if isBool value || isInt value then toString value else
+    if isBool value then if value then "1" else "0" else
+    if isInt value then toString value else
     throw "Unrecognized type ${typeOf value} in htop settings";
 
 in
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 12255d8392fe9..24f368b3e967f 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -5,8 +5,8 @@ let
 
   parentWrapperDir = dirOf wrapperDir;
 
-  securityWrapper = pkgs.callPackage ./wrapper.nix {
-    inherit parentWrapperDir;
+  securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix {
+    inherit sourceProg;
   };
 
   fileModeType =
@@ -91,8 +91,7 @@ let
     , ...
     }:
     ''
-      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
-      echo -n "${source}" > "$wrapperDir/${program}.real"
+      cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
@@ -119,8 +118,7 @@ let
     , ...
     }:
     ''
-      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
-      echo -n "${source}" > "$wrapperDir/${program}.real"
+      cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 17776a97af81e..2cf1727a31c8c 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -17,6 +17,10 @@
 #include <syscall.h>
 #include <byteswap.h>
 
+#ifndef SOURCE_PROG
+#error SOURCE_PROG should be defined via preprocessor commandline
+#endif
+
 // aborts when false, printing the failed expression
 #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
 // aborts when returns non-zero, printing the failed expression and errno
@@ -24,10 +28,6 @@
 
 extern char **environ;
 
-// The WRAPPER_DIR macro is supplied at compile time so that it cannot
-// be changed at runtime
-static char *wrapper_dir = WRAPPER_DIR;
-
 // Wrapper debug variable name
 static char *wrapper_debug = "WRAPPER_DEBUG";
 
@@ -151,115 +151,20 @@ static int make_caps_ambient(const char *self_path) {
     return 0;
 }
 
-int readlink_malloc(const char *p, char **ret) {
-    size_t l = FILENAME_MAX+1;
-    int r;
-
-    for (;;) {
-        char *c = calloc(l, sizeof(char));
-        if (!c) {
-            return -ENOMEM;
-        }
-
-        ssize_t n = readlink(p, c, l-1);
-        if (n < 0) {
-            r = -errno;
-            free(c);
-            return r;
-        }
-
-        if ((size_t) n < l-1) {
-            c[n] = 0;
-            *ret = c;
-            return 0;
-        }
-
-        free(c);
-        l *= 2;
-    }
-}
-
 int main(int argc, char **argv) {
     ASSERT(argc >= 1);
-    char *self_path = NULL;
-    int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
-    if (self_path_size < 0) {
-        fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
-    }
-
-    unsigned int ruid, euid, suid, rgid, egid, sgid;
-    MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
-    MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
-
-    // If true, then we did not benefit from setuid privilege escalation,
-    // where the original uid is still in ruid and different from euid == suid.
-    int didnt_suid = (ruid == euid) && (euid == suid);
-    // If true, then we did not benefit from setgid privilege escalation
-    int didnt_sgid = (rgid == egid) && (egid == sgid);
-
-
-    // Make sure that we are being executed from the right location,
-    // i.e., `safe_wrapper_dir'.  This is to prevent someone from creating
-    // hard link `X' from some other location, along with a false
-    // `X.real' file, to allow arbitrary programs from being executed
-    // with elevated capabilities.
-    int len = strlen(wrapper_dir);
-    if (len > 0 && '/' == wrapper_dir[len - 1])
-      --len;
-    ASSERT(!strncmp(self_path, wrapper_dir, len));
-    ASSERT('/' == wrapper_dir[0]);
-    ASSERT('/' == self_path[len]);
-
-    // If we got privileges with the fs set[ug]id bit, check that the privilege we
-    // got matches the one one we expected, ie that our effective uid/gid
-    // matches the uid/gid of `self_path`. This ensures that we were executed as
-    // `self_path', and not, say, as some other setuid program.
-    // We don't check that if we did not benefit from the set[ug]id bit, as
-    // can be the case in nosuid mounts or user namespaces.
-    struct stat st;
-    ASSERT(lstat(self_path, &st) != -1);
-
-    // if the wrapper gained privilege with suid, check that we got the uid of the file owner
-    ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid));
-    // if the wrapper gained privilege with sgid, check that we got the gid of the file group
-    ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid));
-    // same, but with suid instead of euid
-    ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid));
-    ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid));
-
-    // And, of course, we shouldn't be writable.
-    ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));
-
-    // Read the path of the real (wrapped) program from <self>.real.
-    char real_fn[PATH_MAX + 10];
-    int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
-    ASSERT(real_fn_size < sizeof(real_fn));
-
-    int fd_self = open(real_fn, O_RDONLY);
-    ASSERT(fd_self != -1);
-
-    char source_prog[PATH_MAX];
-    len = read(fd_self, source_prog, PATH_MAX);
-    ASSERT(len != -1);
-    ASSERT(len < sizeof(source_prog));
-    ASSERT(len > 0);
-    source_prog[len] = 0;
-
-    close(fd_self);
 
     // Read the capabilities set on the wrapper and raise them in to
     // the ambient set so the program we're wrapping receives the
     // capabilities too!
-    if (make_caps_ambient(self_path) != 0) {
-        free(self_path);
+    if (make_caps_ambient("/proc/self/exe") != 0) {
         return 1;
     }
-    free(self_path);
 
-    execve(source_prog, argv, environ);
+    execve(SOURCE_PROG, argv, environ);
     
     fprintf(stderr, "%s: cannot run `%s': %s\n",
-        argv[0], source_prog, strerror(errno));
+        argv[0], SOURCE_PROG, strerror(errno));
 
     return 1;
 }
diff --git a/nixos/modules/security/wrappers/wrapper.nix b/nixos/modules/security/wrappers/wrapper.nix
index e3620fb222d2c..aec43412404ea 100644
--- a/nixos/modules/security/wrappers/wrapper.nix
+++ b/nixos/modules/security/wrappers/wrapper.nix
@@ -1,4 +1,4 @@
-{ stdenv, linuxHeaders, parentWrapperDir, debug ? false }:
+{ stdenv, linuxHeaders, sourceProg, debug ? false }:
 # For testing:
 # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }'
 stdenv.mkDerivation {
@@ -7,7 +7,7 @@ stdenv.mkDerivation {
   dontUnpack = true;
   hardeningEnable = [ "pie" ];
   CFLAGS = [
-    ''-DWRAPPER_DIR="${parentWrapperDir}"''
+    ''-DSOURCE_PROG="${sourceProg}"''
   ] ++ (if debug then [
     "-Werror" "-Og" "-g"
   ] else [
diff --git a/nixos/modules/services/audio/goxlr-utility.nix b/nixos/modules/services/audio/goxlr-utility.nix
new file mode 100644
index 0000000000000..b719de875c7ff
--- /dev/null
+++ b/nixos/modules/services/audio/goxlr-utility.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.goxlr-utility;
+in
+
+with lib;
+{
+
+  options = {
+    services.goxlr-utility = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Whether to enable goxlr-utility for controlling your TC-Helicon GoXLR or GoXLR Mini
+        '';
+      };
+      package = mkPackageOptionMD pkgs "goxlr-utility" { };
+      autoStart.xdg = mkOption {
+        default = true;
+        type = with types; bool;
+        description = lib.mdDoc ''
+          Start the daemon automatically using XDG autostart.
+          Sets `xdg.autostart.enable = true` if not already enabled.
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.goxlr-utility.enable
+    {
+      services.udev.packages = [ cfg.package ];
+
+      xdg.autostart.enable = mkIf cfg.autoStart.xdg true;
+      environment.systemPackages = mkIf cfg.autoStart.xdg
+        [
+          cfg.package
+          (pkgs.makeAutostartItem
+            {
+              name = "goxlr-utility";
+              package = cfg.package;
+            })
+        ];
+    };
+
+  meta.maintainers = with maintainers; [ errnoh ];
+}
diff --git a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
index fb70c48991260..d8d25898e294b 100644
--- a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
@@ -31,6 +31,8 @@ let
 
   cfg = config.services.gitea-actions-runner;
 
+  settingsFormat = pkgs.formats.yaml { };
+
   # Check whether any runner instance label requires a container runtime
   # Empty label strings result in the upstream defined defaultLabels, which require docker
   # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
@@ -119,6 +121,18 @@ in
               that follows the filesystem hierarchy standard.
             '';
           };
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Configuration for `act_runner daemon`.
+              See https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml for an example configuration
+            '';
+
+            type = types.submodule {
+              freeformType = settingsFormat.type;
+            };
+
+            default = { };
+          };
 
           hostPackages = mkOption {
             type = listOf package;
@@ -169,6 +183,7 @@ in
         wantsHost = hasHostScheme instance;
         wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
         wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
+        configFile = settingsFormat.generate "config.yaml" instance.settings;
       in
         nameValuePair "gitea-runner-${escapeSystemdPath name}" {
           inherit (instance) enable;
@@ -196,7 +211,12 @@ in
             User = "gitea-runner";
             StateDirectory = "gitea-runner";
             WorkingDirectory = "-/var/lib/gitea-runner/${name}";
-            ExecStartPre = pkgs.writeShellScript "gitea-register-runner-${name}" ''
+
+            # gitea-runner might fail when gitea is restarted during upgrade.
+            Restart = "on-failure";
+            RestartSec = 2;
+
+            ExecStartPre = [(pkgs.writeShellScript "gitea-register-runner-${name}" ''
               export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
               mkdir -vp "$INSTANCE_DIR"
               cd "$INSTANCE_DIR"
@@ -221,8 +241,8 @@ in
                 echo "$LABELS_WANTED" > "$LABELS_FILE"
               fi
 
-            '';
-            ExecStart = "${cfg.package}/bin/act_runner daemon";
+            '')];
+            ExecStart = "${cfg.package}/bin/act_runner daemon --config ${configFile}";
             SupplementaryGroups = optionals (wantsDocker) [
               "docker"
             ] ++ optionals (wantsPodman) [
diff --git a/nixos/modules/services/databases/influxdb2.nix b/nixos/modules/services/databases/influxdb2.nix
index e74de66ddc2f5..3740cd01b5dc0 100644
--- a/nixos/modules/services/databases/influxdb2.nix
+++ b/nixos/modules/services/databases/influxdb2.nix
@@ -1,37 +1,432 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit
+    (lib)
+    any
+    attrNames
+    attrValues
+    count
+    escapeShellArg
+    filterAttrs
+    flatten
+    flip
+    getExe
+    hasAttr
+    hasInfix
+    listToAttrs
+    literalExpression
+    mapAttrsToList
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    nameValuePair
+    optional
+    subtractLists
+    types
+    unique
+    ;
+
   format = pkgs.formats.json { };
   cfg = config.services.influxdb2;
   configFile = format.generate "config.json" cfg.settings;
+
+  validPermissions = [
+    "authorizations"
+    "buckets"
+    "dashboards"
+    "orgs"
+    "tasks"
+    "telegrafs"
+    "users"
+    "variables"
+    "secrets"
+    "labels"
+    "views"
+    "documents"
+    "notificationRules"
+    "notificationEndpoints"
+    "checks"
+    "dbrp"
+    "annotations"
+    "sources"
+    "scrapers"
+    "notebooks"
+    "remotes"
+    "replications"
+  ];
+
+  # Determines whether at least one active api token is defined
+  anyAuthDefined =
+    flip any (attrValues cfg.provision.organizations)
+    (o: o.present && flip any (attrValues o.auths)
+    (a: a.present && a.tokenFile != null));
+
+  provisionState = pkgs.writeText "provision_state.json" (builtins.toJSON {
+    inherit (cfg.provision) organizations users;
+  });
+
+  provisioningScript = pkgs.writeShellScript "post-start-provision" ''
+    set -euo pipefail
+    export INFLUX_HOST="http://"${escapeShellArg (
+      if ! hasAttr "http-bind-address" cfg.settings
+        || hasInfix "0.0.0.0" cfg.settings.http-bind-address
+      then "localhost:8086"
+      else cfg.settings.http-bind-address
+    )}
+
+    # Wait for the influxdb server to come online
+    count=0
+    while ! influx ping &>/dev/null; do
+      if [ "$count" -eq 300 ]; then
+        echo "Tried for 30 seconds, giving up..."
+        exit 1
+      fi
+
+      if ! kill -0 "$MAINPID"; then
+        echo "Main server died, giving up..."
+        exit 1
+      fi
+
+      sleep 0.1
+      count=$((count++))
+    done
+
+    # Do the initial database setup. Pass /dev/null as configs-path to
+    # avoid saving the token as the active config.
+    if test -e "$STATE_DIRECTORY/.first_startup"; then
+      influx setup \
+        --configs-path /dev/null \
+        --org ${escapeShellArg cfg.provision.initialSetup.organization} \
+        --bucket ${escapeShellArg cfg.provision.initialSetup.bucket} \
+        --username ${escapeShellArg cfg.provision.initialSetup.username} \
+        --password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
+        --token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
+        --retention ${toString cfg.provision.initialSetup.retention}s \
+        --force >/dev/null
+
+      rm -f "$STATE_DIRECTORY/.first_startup"
+    fi
+
+    provision_result=$(${getExe pkgs.influxdb2-provision} ${provisionState} "$INFLUX_HOST" "$(< "$CREDENTIALS_DIRECTORY/admin-token")")
+    if [[ "$(jq '[.auths[] | select(.action == "created")] | length' <<< "$provision_result")" -gt 0 ]]; then
+      echo "Created at least one new token, queueing service restart so we can manipulate secrets"
+      touch "$STATE_DIRECTORY/.needs_restart"
+    fi
+  '';
+
+  restarterScript = pkgs.writeShellScript "post-start-restarter" ''
+    set -euo pipefail
+    if test -e "$STATE_DIRECTORY/.needs_restart"; then
+      rm -f "$STATE_DIRECTORY/.needs_restart"
+      /run/current-system/systemd/bin/systemctl restart influxdb2
+    fi
+  '';
+
+  organizationSubmodule = types.submodule (organizationSubmod: let
+    org = organizationSubmod.config._module.args.name;
+  in {
+    options = {
+      present = mkOption {
+        description = mdDoc "Whether to ensure that this organization is present or absent.";
+        type = types.bool;
+        default = true;
+      };
+
+      description = mkOption {
+        description = mdDoc "Optional description for the organization.";
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      buckets = mkOption {
+        description = mdDoc "Buckets to provision in this organization.";
+        default = {};
+        type = types.attrsOf (types.submodule (bucketSubmod: let
+          bucket = bucketSubmod.config._module.args.name;
+        in {
+          options = {
+            present = mkOption {
+              description = mdDoc "Whether to ensure that this bucket is present or absent.";
+              type = types.bool;
+              default = true;
+            };
+
+            description = mkOption {
+              description = mdDoc "Optional description for the bucket.";
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            retention = mkOption {
+              type = types.ints.unsigned;
+              default = 0;
+              description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
+            };
+          };
+        }));
+      };
+
+      auths = mkOption {
+        description = mdDoc "API tokens to provision for the user in this organization.";
+        default = {};
+        type = types.attrsOf (types.submodule (authSubmod: let
+          auth = authSubmod.config._module.args.name;
+        in {
+          options = {
+            id = mkOption {
+              description = mdDoc "A unique identifier for this authentication token. Since influx doesn't store names for tokens, this will be hashed and appended to the description to identify the token.";
+              readOnly = true;
+              default = builtins.substring 0 32 (builtins.hashString "sha256" "${org}:${auth}");
+              defaultText = "<a hash derived from org and name>";
+              type = types.str;
+            };
+
+            present = mkOption {
+              description = mdDoc "Whether to ensure that this user is present or absent.";
+              type = types.bool;
+              default = true;
+            };
+
+            description = mkOption {
+              description = ''
+                Optional description for the API token.
+                Note that the actual token will always be created with a descriptionregardless
+                of whether this is given or not. The name is always added plus a unique suffix
+                to later identify the token to track whether it has already been created.
+              '';
+              default = null;
+              type = types.nullOr types.str;
+            };
+
+            tokenFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc "The token value. If not given, influx will automatically generate one.";
+            };
+
+            operator = mkOption {
+              description = mdDoc "Grants all permissions in all organizations.";
+              default = false;
+              type = types.bool;
+            };
+
+            allAccess = mkOption {
+              description = mdDoc "Grants all permissions in the associated organization.";
+              default = false;
+              type = types.bool;
+            };
+
+            readPermissions = mkOption {
+              description = mdDoc ''
+                The read permissions to include for this token. Access is usually granted only
+                for resources in the associated organization.
+
+                Available permissions are `authorizations`, `buckets`, `dashboards`,
+                `orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
+                `documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
+                `annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
+
+                Refer to `influx auth create --help` for a full list with descriptions.
+
+                `buckets` grants read access to all associated buckets. Use `readBuckets` to define
+                more granular access permissions.
+              '';
+              default = [];
+              type = types.listOf (types.enum validPermissions);
+            };
+
+            writePermissions = mkOption {
+              description = mdDoc ''
+                The read permissions to include for this token. Access is usually granted only
+                for resources in the associated organization.
+
+                Available permissions are `authorizations`, `buckets`, `dashboards`,
+                `orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
+                `documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
+                `annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
+
+                Refer to `influx auth create --help` for a full list with descriptions.
+
+                `buckets` grants write access to all associated buckets. Use `writeBuckets` to define
+                more granular access permissions.
+              '';
+              default = [];
+              type = types.listOf (types.enum validPermissions);
+            };
+
+            readBuckets = mkOption {
+              description = mdDoc "The organization's buckets which should be allowed to be read";
+              default = [];
+              type = types.listOf types.str;
+            };
+
+            writeBuckets = mkOption {
+              description = mdDoc "The organization's buckets which should be allowed to be written";
+              default = [];
+              type = types.listOf types.str;
+            };
+          };
+        }));
+      };
+    };
+  });
 in
 {
   options = {
     services.influxdb2 = {
-      enable = mkEnableOption (lib.mdDoc "the influxdb2 server");
+      enable = mkEnableOption (mdDoc "the influxdb2 server");
 
       package = mkOption {
         default = pkgs.influxdb2-server;
         defaultText = literalExpression "pkgs.influxdb2";
-        description = lib.mdDoc "influxdb2 derivation to use.";
+        description = mdDoc "influxdb2 derivation to use.";
         type = types.package;
       };
 
       settings = mkOption {
         default = { };
-        description = lib.mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
+        description = mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
         type = format.type;
       };
+
+      provision = {
+        enable = mkEnableOption "initial database setup and provisioning";
+
+        initialSetup = {
+          organization = mkOption {
+            type = types.str;
+            example = "main";
+            description = mdDoc "Primary organization name";
+          };
+
+          bucket = mkOption {
+            type = types.str;
+            example = "example";
+            description = mdDoc "Primary bucket name";
+          };
+
+          username = mkOption {
+            type = types.str;
+            default = "admin";
+            description = mdDoc "Primary username";
+          };
+
+          retention = mkOption {
+            type = types.ints.unsigned;
+            default = 0;
+            description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
+          };
+
+          passwordFile = mkOption {
+            type = types.path;
+            description = mdDoc "Password for primary user. Don't use a file from the nix store!";
+          };
+
+          tokenFile = mkOption {
+            type = types.path;
+            description = mdDoc "API Token to set for the admin user. Don't use a file from the nix store!";
+          };
+        };
+
+        organizations = mkOption {
+          description = mdDoc "Organizations to provision.";
+          example = literalExpression ''
+            {
+              myorg = {
+                description = "My organization";
+                buckets.mybucket = {
+                  description = "My bucket";
+                  retention = 31536000; # 1 year
+                };
+                auths.mytoken = {
+                  readBuckets = ["mybucket"];
+                  tokenFile = "/run/secrets/mytoken";
+                };
+              };
+            }
+          '';
+          default = {};
+          type = types.attrsOf organizationSubmodule;
+        };
+
+        users = mkOption {
+          description = mdDoc "Users to provision.";
+          default = {};
+          example = literalExpression ''
+            {
+              # admin = {}; /* The initialSetup.username will automatically be added. */
+              myuser.passwordFile = "/run/secrets/myuser_password";
+            }
+          '';
+          type = types.attrsOf (types.submodule (userSubmod: let
+            user = userSubmod.config._module.args.name;
+            org = userSubmod.config.org;
+          in {
+            options = {
+              present = mkOption {
+                description = mdDoc "Whether to ensure that this user is present or absent.";
+                type = types.bool;
+                default = true;
+              };
+
+              passwordFile = mkOption {
+                description = mdDoc "Password for the user. If unset, the user will not be able to log in until a password is set by an operator! Don't use a file from the nix store!";
+                default = null;
+                type = types.nullOr types.path;
+              };
+            };
+          }));
+        };
+      };
     };
   };
 
   config = mkIf cfg.enable {
-    assertions = [{
-      assertion = !(builtins.hasAttr "bolt-path" cfg.settings) && !(builtins.hasAttr "engine-path" cfg.settings);
-      message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
-    }];
+    assertions =
+      [
+        {
+          assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
+          message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
+        }
+      ]
+      ++ flatten (flip mapAttrsToList cfg.provision.organizations (orgName: org:
+        flip mapAttrsToList org.auths (authName: auth:
+          [
+            {
+              assertion = 1 == count (x: x) [
+                auth.operator
+                auth.allAccess
+                (auth.readPermissions != []
+                  || auth.writePermissions != []
+                  || auth.readBuckets != []
+                  || auth.writeBuckets != [])
+              ];
+              message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: The `operator` and `allAccess` options are mutually exclusive with each other and the granular permission settings.";
+            }
+            (let unknownBuckets = subtractLists (attrNames org.buckets) auth.readBuckets; in {
+              assertion = unknownBuckets == [];
+              message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in readBuckets: ${toString unknownBuckets}";
+            })
+            (let unknownBuckets = subtractLists (attrNames org.buckets) auth.writeBuckets; in {
+              assertion = unknownBuckets == [];
+              message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in writeBuckets: ${toString unknownBuckets}";
+            })
+          ]
+        )
+      ));
+
+    services.influxdb2.provision = mkIf cfg.provision.enable {
+      organizations.${cfg.provision.initialSetup.organization} = {
+        buckets.${cfg.provision.initialSetup.bucket} = {
+          inherit (cfg.provision.initialSetup) retention;
+        };
+      };
+      users.${cfg.provision.initialSetup.username} = {
+        inherit (cfg.provision.initialSetup) passwordFile;
+      };
+    };
 
     systemd.services.influxdb2 = {
       description = "InfluxDB is an open-source, distributed, time series database";
@@ -52,7 +447,42 @@ in
         LimitNOFILE = 65536;
         KillMode = "control-group";
         Restart = "on-failure";
+        LoadCredential = mkIf cfg.provision.enable [
+          "admin-password:${cfg.provision.initialSetup.passwordFile}"
+          "admin-token:${cfg.provision.initialSetup.tokenFile}"
+        ];
+
+        ExecStartPost = mkIf cfg.provision.enable (
+          [provisioningScript] ++
+          # Only the restarter runs with elevated privileges
+          optional anyAuthDefined "+${restarterScript}"
+        );
       };
+
+      path = [
+        pkgs.influxdb2-cli
+        pkgs.jq
+      ];
+
+      # Mark if this is the first startup so postStart can do the initial setup.
+      # Also extract any token secret mappings and apply them if this isn't the first start.
+      preStart = let
+        tokenPaths = listToAttrs (flatten
+          # For all organizations
+          (flip mapAttrsToList cfg.provision.organizations
+            # For each contained token that has a token file
+            (_: org: flip mapAttrsToList (filterAttrs (_: x: x.tokenFile != null) org.auths)
+              # Collect id -> tokenFile for the mapping
+              (_: auth: nameValuePair auth.id auth.tokenFile))));
+        tokenMappings = pkgs.writeText "token_mappings.json" (builtins.toJSON tokenPaths);
+      in mkIf cfg.provision.enable ''
+        if ! test -e "$STATE_DIRECTORY/influxd.bolt"; then
+          touch "$STATE_DIRECTORY/.first_startup"
+        else
+          # Manipulate provisioned api tokens if necessary
+          ${getExe pkgs.influxdb2-token-manipulator} "$STATE_DIRECTORY/influxd.bolt" ${tokenMappings}
+        fi
+      '';
     };
 
     users.extraUsers.influxdb2 = {
@@ -63,5 +493,5 @@ in
     users.extraGroups.influxdb2 = {};
   };
 
-  meta.maintainers = with lib.maintainers; [ nickcao ];
+  meta.maintainers = with lib.maintainers; [ nickcao oddlama ];
 }
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index fe3a101597947..fad4f39ff2104 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -80,6 +80,15 @@ in
         using the EDITOR environment variable.
       '';
     };
+
+    startWithGraphical = mkOption {
+      type = types.bool;
+      default = config.services.xserver.enable;
+      defaultText = literalExpression "config.services.xserver.enable";
+      description = lib.mdDoc ''
+        Start emacs with the graphical session instead of any session. Without this, emacs clients will not be able to create frames in the graphical session.
+      '';
+    };
   };
 
   config = mkIf (cfg.enable || cfg.install) {
@@ -92,7 +101,13 @@ in
         ExecStop = "${cfg.package}/bin/emacsclient --eval (kill-emacs)";
         Restart = "always";
       };
-    } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
+
+      unitConfig = optionalAttrs cfg.startWithGraphical {
+        After = "graphical-session.target";
+      };
+    } // optionalAttrs cfg.enable {
+      wantedBy = if cfg.startWithGraphical then [ "graphical-session.target" ] else [ "default.target" ];
+    };
 
     environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
 
diff --git a/nixos/modules/services/games/openarena.nix b/nixos/modules/services/games/openarena.nix
index 89e30d7c12afd..8f6d4986903f6 100644
--- a/nixos/modules/services/games/openarena.nix
+++ b/nixos/modules/services/games/openarena.nix
@@ -1,14 +1,14 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) concatStringsSep mkEnableOption mkIf mkOption types;
   cfg = config.services.openarena;
 in
 {
   options = {
     services.openarena = {
       enable = mkEnableOption (lib.mdDoc "OpenArena");
+      package = lib.mkPackageOptionMD pkgs "openarena" { };
 
       openPorts = mkOption {
         type = types.bool;
@@ -43,7 +43,7 @@ in
       serviceConfig = {
         DynamicUser = true;
         StateDirectory = "openarena";
-        ExecStart = "${pkgs.openarena}/bin/oa_ded +set fs_basepath ${pkgs.openarena}/openarena-0.8.8 +set fs_homepath /var/lib/openarena ${concatStringsSep " " cfg.extraFlags}";
+        ExecStart = "${cfg.package}/bin/oa_ded +set fs_basepath ${cfg.package}/share/openarena +set fs_homepath /var/lib/openarena ${concatStringsSep " " cfg.extraFlags}";
         Restart = "on-failure";
 
         # Hardening
diff --git a/nixos/modules/services/games/quake3-server.nix b/nixos/modules/services/games/quake3-server.nix
index 2d2148237da16..e51830c12e788 100644
--- a/nixos/modules/services/games/quake3-server.nix
+++ b/nixos/modules/services/games/quake3-server.nix
@@ -1,13 +1,15 @@
 { config, pkgs, lib, ... }:
-with lib;
 
 let
+  inherit (lib) literalMD mkEnableOption mkIf mkOption types;
   cfg = config.services.quake3-server;
+
   configFile = pkgs.writeText "q3ds-extra.cfg" ''
     set net_port ${builtins.toString cfg.port}
 
     ${cfg.extraConfig}
   '';
+
   defaultBaseq3 = pkgs.requireFile rec {
     name = "baseq3";
     hashMode = "recursive";
@@ -25,6 +27,7 @@ let
       $services.quake3-server.baseq3/.q3a/
     '';
   };
+
   home = pkgs.runCommand "quake3-home" {} ''
       mkdir -p $out/.q3a/baseq3
 
@@ -38,6 +41,7 @@ in {
   options = {
     services.quake3-server = {
       enable = mkEnableOption (lib.mdDoc "Quake 3 dedicated server");
+      package = lib.mkPackageOptionMD pkgs "ioquake3" { };
 
       port = mkOption {
         type = types.port;
@@ -103,10 +107,10 @@ in {
         ReadOnlyPaths = if baseq3InStore then home else cfg.baseq3;
         ExecStartPre = optionalString (!baseq3InStore) "+${pkgs.coreutils}/bin/cp ${configFile} ${cfg.baseq3}/.q3a/baseq3/nix.cfg";
 
-        ExecStart = "${pkgs.ioquake3}/ioq3ded.x86_64 +exec nix.cfg";
+        ExecStart = "${cfg.package}/bin/ioq3ded +exec nix.cfg";
       };
     };
   };
 
-  meta.maintainers = with maintainers; [ f4814n ];
+  meta.maintainers = with lib.maintainers; [ f4814n ];
 }
diff --git a/nixos/modules/services/hardware/hddfancontrol.nix b/nixos/modules/services/hardware/hddfancontrol.nix
new file mode 100644
index 0000000000000..463f63cc4940f
--- /dev/null
+++ b/nixos/modules/services/hardware/hddfancontrol.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.hddfancontrol;
+  types = lib.types;
+in
+
+{
+  options = {
+
+    services.hddfancontrol.enable = lib.mkEnableOption "hddfancontrol daemon";
+
+    services.hddfancontrol.disks = lib.mkOption {
+      type = with types; listOf path;
+      default = [];
+      description = lib.mdDoc ''
+        Drive(s) to get temperature from
+      '';
+      example = ["/dev/sda"];
+    };
+
+    services.hddfancontrol.pwmPaths = lib.mkOption {
+      type = with types; listOf path;
+      default = [];
+      description = lib.mdDoc ''
+        PWM filepath(s) to control fan speed (under /sys)
+      '';
+      example = ["/sys/class/hwmon/hwmon2/pwm1"];
+    };
+
+    services.hddfancontrol.smartctl = lib.mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Probe temperature using smartctl instead of hddtemp or hdparm
+      '';
+    };
+
+    services.hddfancontrol.extraArgs = lib.mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = lib.mdDoc ''
+        Extra commandline arguments for hddfancontrol
+      '';
+      example = ["--pwm-start-value=32"
+                 "--pwm-stop-value=0"
+                 "--spin-down-time=900"];
+    };
+  };
+
+  config = lib.mkIf cfg.enable (
+    let args = lib.concatLists [
+      ["-d"] cfg.disks
+      ["-p"] cfg.pwmPaths
+      (lib.optional cfg.smartctl "--smartctl")
+      cfg.extraArgs
+    ]; in {
+      systemd.packages = [pkgs.hddfancontrol];
+
+      systemd.services.hddfancontrol = {
+        enable = true;
+        wantedBy = [ "multi-user.target" ];
+        environment.HDDFANCONTROL_ARGS = lib.escapeShellArgs args;
+      };
+    }
+  );
+}
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 1eb51c50ff79c..673930c4cb5c6 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -37,8 +37,8 @@ in
 
       package = mkOption {
         type = types.package;
-        default = if versionOlder config.system.stateVersion "23.05" then pkgs.graylog-3_3 else pkgs.graylog-5_0;
-        defaultText = literalExpression (if versionOlder config.system.stateVersion "23.05" then "pkgs.graylog-3_3" else "pkgs.graylog-5_0");
+        default = if versionOlder config.system.stateVersion "23.05" then pkgs.graylog-3_3 else pkgs.graylog-5_1;
+        defaultText = literalExpression (if versionOlder config.system.stateVersion "23.05" then "pkgs.graylog-3_3" else "pkgs.graylog-5_1");
         description = lib.mdDoc "Graylog package to use.";
       };
 
diff --git a/nixos/modules/services/matrix/conduit.nix b/nixos/modules/services/matrix/conduit.nix
index 16c4f571da941..76af7ba228579 100644
--- a/nixos/modules/services/matrix/conduit.nix
+++ b/nixos/modules/services/matrix/conduit.nix
@@ -94,6 +94,16 @@ in
                 instance will require manual migration of data.
               '';
             };
+            global.allow_check_for_updates = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Whether to allow Conduit to automatically contact
+                <https://conduit.rs> hourly to check for important Conduit news.
+
+                Disabled by default because nixpkgs handles updates.
+              '';
+            };
           };
         };
         default = {};
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index 3dca3ff94f213..ef69a8adebb01 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -9,11 +9,6 @@ let
   # remove null values from the final configuration
   finalSettings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
   configFile = format.generate "homeserver.yaml" finalSettings;
-  logConfigFile = format.generate "log_config.yaml" cfg.logConfig;
-
-  pluginsEnv = cfg.package.python.buildEnv.override {
-    extraLibs = cfg.plugins;
-  };
 
   usePostgresql = cfg.settings.database.name == "psycopg2";
   hasLocalPostgresDB = let args = cfg.settings.database.args; in
@@ -50,6 +45,29 @@ let
             "${bindAddress}"
         }:${builtins.toString listener.port}/"
     '';
+
+  defaultExtras = [
+    "systemd"
+    "postgres"
+    "url-preview"
+    "user-search"
+  ];
+
+  wantedExtras = cfg.extras
+    ++ lib.optional (cfg.settings ? oidc_providers) "oidc"
+    ++ lib.optional (cfg.settings ? jwt_config) "jwt"
+    ++ lib.optional (cfg.settings ? saml2_config) "saml2"
+    ++ lib.optional (cfg.settings ? opentracing) "opentracing"
+    ++ lib.optional (cfg.settings ? redis) "redis"
+    ++ lib.optional (cfg.settings ? sentry) "sentry"
+    ++ lib.optional (cfg.settings ? user_directory) "user-search"
+    ++ lib.optional (cfg.settings.url_preview_enabled) "url-preview"
+    ++ lib.optional (cfg.settings.database.name == "psycopg2") "postgres";
+
+  wrapped = pkgs.matrix-synapse.override {
+    extras = wantedExtras;
+    inherit (cfg) plugins;
+  };
 in {
 
   imports = [
@@ -151,10 +169,53 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.matrix-synapse;
-        defaultText = literalExpression "pkgs.matrix-synapse";
+        readOnly = true;
+        description = lib.mdDoc ''
+          Reference to the `matrix-synapse` wrapper with all extras
+          (e.g. for `oidc` or `saml2`) added to the `PYTHONPATH` of all executables.
+
+          This option is useful to reference the "final" `matrix-synapse` package that's
+          actually used by `matrix-synapse.service`. For instance, when using
+          workers, it's possible to run
+          `''${config.services.matrix-synapse.package}/bin/synapse_worker` and
+          no additional PYTHONPATH needs to be specified for extras or plugins configured
+          via `services.matrix-synapse`.
+
+          However, this means that this option is supposed to be only declared
+          by the `services.matrix-synapse` module itself and is thus read-only.
+          In order to modify `matrix-synapse` itself, use an overlay to override
+          `pkgs.matrix-synapse-unwrapped`.
+        '';
+      };
+
+      extras = mkOption {
+        type = types.listOf (types.enum (lib.attrNames pkgs.matrix-synapse-unwrapped.optional-dependencies));
+        default = defaultExtras;
+        example = literalExpression ''
+          [
+            "cache-memory" # Provide statistics about caching memory consumption
+            "jwt"          # JSON Web Token authentication
+            "opentracing"  # End-to-end tracing support using Jaeger
+            "oidc"         # OpenID Connect authentication
+            "postgres"     # PostgreSQL database backend
+            "redis"        # Redis support for the replication stream between worker processes
+            "saml2"        # SAML2 authentication
+            "sentry"       # Error tracking and performance metrics
+            "systemd"      # Provide the JournalHandler used in the default log_config
+            "url-preview"  # Support for oEmbed URL previews
+            "user-search"  # Support internationalized domain names in user-search
+          ]
+        '';
         description = lib.mdDoc ''
-          Overridable attribute of the matrix synapse server package to use.
+          Explicitly install extras provided by matrix-synapse. Most
+          will require some additional configuration.
+
+          Extras will automatically be enabled, when the relevant
+          configuration sections are present.
+
+          Please note that this option is additive: i.e. when adding a new item
+          to this list, the defaults are still kept. To override the defaults as well,
+          use `lib.mkForce`.
         '';
       };
 
@@ -193,7 +254,7 @@ in {
         default = {};
         description = mdDoc ''
           The primary synapse configuration. See the
-          [sample configuration](https://github.com/matrix-org/synapse/blob/v${cfg.package.version}/docs/sample_config.yaml)
+          [sample configuration](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml)
           for possible values.
 
           Secrets should be passed in by using the `extraConfigFiles` option.
@@ -706,6 +767,10 @@ in {
     ];
 
     services.matrix-synapse.configFile = configFile;
+    services.matrix-synapse.package = wrapped;
+
+    # default them, so they are additive
+    services.matrix-synapse.extras = defaultExtras;
 
     users.users.matrix-synapse = {
       group = "matrix-synapse";
@@ -729,9 +794,7 @@ in {
           --keys-directory ${cfg.dataDir} \
           --generate-keys
       '';
-      environment = {
-        PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
-      } // optionalAttrs (cfg.withJemalloc) {
+      environment = optionalAttrs (cfg.withJemalloc) {
         LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
       };
       serviceConfig = {
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 616a60a123ea9..b2e4e760d8287 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -351,7 +351,7 @@ in {
           CacheDirectory = dirs cacheDirs;
           RuntimeDirectory = dirName;
           ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ];
-          StateDirectory = dirs (lib.optional (!useCustomDir) libDirs);
+          StateDirectory = dirs (lib.optionals (!useCustomDir) libDirs);
           LogsDirectory = dirName;
           PrivateTmp = true;
           ProtectSystem = "strict";
diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix
index e0b686434b6e9..208eb356d629d 100644
--- a/nixos/modules/services/networking/haproxy.nix
+++ b/nixos/modules/services/networking/haproxy.nix
@@ -17,14 +17,9 @@ with lib;
   options = {
     services.haproxy = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to enable HAProxy, the reliable, high performance TCP/HTTP
-          load balancer.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "HAProxy, the reliable, high performance TCP/HTTP load balancer.");
+
+      package = mkPackageOptionMD pkgs "haproxy" { };
 
       user = mkOption {
         type = types.str;
@@ -70,15 +65,15 @@ with lib;
         ExecStartPre = [
           # when the master process receives USR2, it reloads itself using exec(argv[0]),
           # so we create a symlink there and update it before reloading
-          "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
+          "${pkgs.coreutils}/bin/ln -sf ${lib.getExe cfg.package} /run/haproxy/haproxy"
           # when running the config test, don't be quiet so we can see what goes wrong
           "/run/haproxy/haproxy -c -f ${haproxyCfg}"
         ];
         ExecStart = "/run/haproxy/haproxy -Ws -f /etc/haproxy.cfg -p /run/haproxy/haproxy.pid";
         # support reloading
         ExecReload = [
-          "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}"
-          "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
+          "${lib.getExe cfg.package} -c -f ${haproxyCfg}"
+          "${pkgs.coreutils}/bin/ln -sf ${lib.getExe cfg.package} /run/haproxy/haproxy"
           "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"
         ];
         KillMode = "mixed";
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 78253dd9d112e..03e6f86af53f2 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -292,7 +292,7 @@ in {
               };
 
               client_secret_path = mkOption {
-                type = types.nullOr types.path;
+                type = types.nullOr types.str;
                 default = null;
                 description = lib.mdDoc ''
                   Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}.
diff --git a/nixos/modules/services/networking/jool.nix b/nixos/modules/services/networking/jool.nix
new file mode 100644
index 0000000000000..3aafbe40967ce
--- /dev/null
+++ b/nixos/modules/services/networking/jool.nix
@@ -0,0 +1,222 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.networking.jool;
+
+  jool = config.boot.kernelPackages.jool;
+  jool-cli = pkgs.jool-cli;
+
+  hardening = {
+    # Run as unprivileged user
+    User = "jool";
+    Group = "jool";
+    DynamicUser = true;
+
+    # Restrict filesystem to only read the jool module
+    TemporaryFileSystem = [ "/" ];
+    BindReadOnlyPaths = [
+      builtins.storeDir
+      "/run/current-system/kernel-modules"
+    ];
+
+    # Give capabilities to load the module and configure it
+    AmbientCapabilities = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ];
+    RestrictAddressFamilies = [ "AF_NETLINK" ];
+
+    # Other restrictions
+    RestrictNamespaces = [ "net" ];
+    SystemCallFilter = [ "@system-service" "@module" ];
+    CapabilityBoundingSet = [ "CAP_SYS_MODULE" "CAP_NET_ADMIN" ];
+  };
+
+  configFormat = pkgs.formats.json {};
+
+  mkDefaultAttrs = lib.mapAttrs (n: v: lib.mkDefault v);
+
+  defaultNat64 = {
+    instance = "default";
+    framework = "netfilter";
+    global.pool6 = "64:ff9b::/96";
+  };
+  defaultSiit = {
+    instance = "default";
+    framework = "netfilter";
+  };
+
+  nat64Conf = configFormat.generate "jool-nat64.conf" cfg.nat64.config;
+  siitConf  = configFormat.generate "jool-siit.conf" cfg.siit.config;
+
+in
+
+{
+  ###### interface
+
+  options = {
+    networking.jool.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      relatedPackages = [ "linuxPackages.jool" "jool-cli" ];
+      description = lib.mdDoc ''
+        Whether to enable Jool, an Open Source implementation of IPv4/IPv6
+        translation on Linux.
+
+        Jool can perform stateless IP/ICMP translation (SIIT) or stateful
+        NAT64, analogous to the IPv4 NAPT. Refer to the upstream
+        [documentation](https://nicmx.github.io/Jool/en/intro-xlat.html) for
+        the supported modes of translation and how to configure them.
+      '';
+    };
+
+    networking.jool.nat64.enable = lib.mkEnableOption (lib.mdDoc "a NAT64 instance of Jool.");
+    networking.jool.nat64.config = lib.mkOption {
+      type = configFormat.type;
+      default = defaultNat64;
+      example = lib.literalExpression ''
+        {
+          # custom NAT64 prefix
+          global.pool6 = "2001:db8:64::/96";
+
+          # Port forwarding
+          bib = [
+            { # SSH 192.0.2.16 → 2001:db8:a::1
+              "protocol"     = "TCP";
+              "ipv4 address" = "192.0.2.16#22";
+              "ipv6 address" = "2001:db8:a::1#22";
+            }
+            { # DNS (TCP) 192.0.2.16 → 2001:db8:a::2
+              "protocol"     = "TCP";
+              "ipv4 address" = "192.0.2.16#53";
+              "ipv6 address" = "2001:db8:a::2#53";
+            }
+            { # DNS (UDP) 192.0.2.16 → 2001:db8:a::2
+              "protocol" = "UDP";
+              "ipv4 address" = "192.0.2.16#53";
+              "ipv6 address" = "2001:db8:a::2#53";
+            }
+          ];
+
+          pool4 = [
+            # Ports for dynamic translation
+            { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+            { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+            { protocol = "ICMP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+
+            # Ports for static BIB entries
+            { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "22"; }
+            { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "53"; }
+          ];
+        }
+      '';
+      description = lib.mdDoc ''
+        The configuration of a stateful NAT64 instance of Jool managed through
+        NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the
+        available options.
+
+        ::: {.note}
+        Existing or more instances created manually will not interfere with the
+        NixOS instance, provided the respective `pool4` addresses and port
+        ranges are not overlapping.
+        :::
+
+        ::: {.warning}
+        Changes to the NixOS instance performed via `jool instance nixos-nat64`
+        are applied correctly but will be lost after restarting
+        `jool-nat64.service`.
+        :::
+      '';
+    };
+
+    networking.jool.siit.enable = lib.mkEnableOption (lib.mdDoc "a SIIT instance of Jool.");
+    networking.jool.siit.config = lib.mkOption {
+      type = configFormat.type;
+      default = defaultSiit;
+      example = lib.literalExpression ''
+        {
+          # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
+          pool6 = "2001:db8::/96";
+
+          # Explicit address mappings
+          eamt = [
+            # 2001:db8:1:: ←→ 192.0.2.0
+            { "ipv6 prefix": "2001:db8:1::/128", "ipv4 prefix": "192.0.2.0" }
+            # 2001:db8:1::x ←→ 198.51.100.x
+            { "ipv6 prefix": "2001:db8:2::/120", "ipv4 prefix": "198.51.100.0/24" }
+          ]
+        }
+      '';
+      description = lib.mdDoc ''
+        The configuration of a SIIT instance of Jool managed through
+        NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the
+        available options.
+
+        ::: {.note}
+        Existing or more instances created manually will not interfere with the
+        NixOS instance, provided the respective `EAMT` address mappings are not
+        overlapping.
+        :::
+
+        ::: {.warning}
+        Changes to the NixOS instance performed via `jool instance nixos-siit`
+        are applied correctly but will be lost after restarting
+        `jool-siit.service`.
+        :::
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ jool-cli ];
+    boot.extraModulePackages = [ jool ];
+
+    systemd.services.jool-nat64 = lib.mkIf cfg.nat64.enable {
+      description = "Jool, NAT64 setup";
+      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool";
+        ExecStart    = "${jool-cli}/bin/jool file handle ${nat64Conf}";
+        ExecStop     = "${jool-cli}/bin/jool -f ${nat64Conf} instance remove";
+      } // hardening;
+    };
+
+    systemd.services.jool-siit = lib.mkIf cfg.siit.enable {
+      description = "Jool, SIIT setup";
+      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit";
+        ExecStart    = "${jool-cli}/bin/jool_siit file handle ${siitConf}";
+        ExecStop     = "${jool-cli}/bin/jool_siit -f ${siitConf} instance remove";
+      } // hardening;
+    };
+
+    system.checks = lib.singleton (pkgs.runCommand "jool-validated" {
+      nativeBuildInputs = [ pkgs.buildPackages.jool-cli ];
+      preferLocalBuild = true;
+    } ''
+      printf 'Validating Jool configuration... '
+      ${lib.optionalString cfg.siit.enable "jool_siit file check ${siitConf}"}
+      ${lib.optionalString cfg.nat64.enable "jool file check ${nat64Conf}"}
+      printf 'ok\n'
+      touch "$out"
+    '');
+
+    networking.jool.nat64.config = mkDefaultAttrs defaultNat64;
+    networking.jool.siit.config  = mkDefaultAttrs defaultSiit;
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index f308b7e331140..8b35cc8d66697 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.tailscale;
   isNetworkd = config.networking.useNetworkd;
 in {
-  meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 ];
+  meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 mfrw ];
 
   options.services.tailscale = {
     enable = mkEnableOption (lib.mdDoc "Tailscale client daemon");
diff --git a/nixos/modules/services/networking/twingate.nix b/nixos/modules/services/networking/twingate.nix
index 170d392bf2135..03c68fc874f02 100644
--- a/nixos/modules/services/networking/twingate.nix
+++ b/nixos/modules/services/networking/twingate.nix
@@ -17,7 +17,7 @@ in
     };
 
     networking.firewall.checkReversePath = lib.mkDefault "loose";
-    services.resolved.enable = !(config.networking.networkmanager.enable);
+    services.resolved.enable = lib.mkIf (!config.networking.networkmanager.enable) true;
 
     environment.systemPackages = [ cfg.package ]; # For the CLI.
   };
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
index 6fb9f71a489e2..d8a99dee59f44 100644
--- a/nixos/modules/services/security/kanidm.nix
+++ b/nixos/modules/services/security/kanidm.nix
@@ -69,6 +69,8 @@ in
     enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server");
     enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration");
 
+    package = lib.mkPackageOptionMD pkgs "kanidm" {};
+
     serverSettings = lib.mkOption {
       type = lib.types.submodule {
         freeformType = settingsFormat.type;
@@ -222,7 +224,7 @@ in
         }
       ];
 
-    environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ];
+    environment.systemPackages = lib.mkIf cfg.enableClient [ cfg.package ];
 
     systemd.services.kanidm = lib.mkIf cfg.enableServer {
       description = "kanidm identity management daemon";
@@ -237,7 +239,7 @@ in
           StateDirectory = "kanidm";
           StateDirectoryMode = "0700";
           RuntimeDirectory = "kanidmd";
-          ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
+          ExecStart = "${cfg.package}/bin/kanidmd server -c ${serverConfigFile}";
           User = "kanidm";
           Group = "kanidm";
 
@@ -270,7 +272,7 @@ in
           CacheDirectory = "kanidm-unixd";
           CacheDirectoryMode = "0700";
           RuntimeDirectory = "kanidm-unixd";
-          ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
+          ExecStart = "${cfg.package}/bin/kanidm_unixd";
           User = "kanidm-unixd";
           Group = "kanidm-unixd";
 
@@ -302,7 +304,7 @@ in
       partOf = [ "kanidm-unixd.service" ];
       restartTriggers = [ unixConfigFile clientConfigFile ];
       serviceConfig = {
-        ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks";
+        ExecStart = "${cfg.package}/bin/kanidm_unixd_tasks";
 
         BindReadOnlyPaths = [
           "/nix/store"
@@ -346,7 +348,7 @@ in
       })
     ];
 
-    system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ];
+    system.nssModules = lib.mkIf cfg.enablePam [ cfg.package ];
 
     system.nssDatabases.group = lib.optional cfg.enablePam "kanidm";
     system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm";
@@ -365,7 +367,7 @@ in
           description = "Kanidm server";
           isSystemUser = true;
           group = "kanidm";
-          packages = with pkgs; [ kanidm ];
+          packages = [ cfg.package ];
         };
       })
       (lib.mkIf cfg.enablePam {
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 12547acabfe05..718c3d2498eac 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -579,7 +579,7 @@ in
       description = "OAuth2 Proxy";
       path = [ cfg.package ];
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
+      after = [ "network-online.target" ];
 
       serviceConfig = {
         User = "oauth2_proxy";
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
index 98695b1ef0603..013aeb16756cd 100644
--- a/nixos/modules/services/security/opensnitch.nix
+++ b/nixos/modules/services/security/opensnitch.nix
@@ -147,7 +147,7 @@ in {
   config = mkIf cfg.enable {
 
     # pkg.opensnitch is referred to elsewhere in the module so we don't need to worry about it being garbage collected
-    services.opensnitch.settings = mapAttrs (_: v: mkDefault v) (builtins.fromJSON (builtins.unsafeDiscardStringContext (builtins.readFile "${pkgs.opensnitch}/etc/default-config.json")));
+    services.opensnitch.settings = mapAttrs (_: v: mkDefault v) (builtins.fromJSON (builtins.unsafeDiscardStringContext (builtins.readFile "${pkgs.opensnitch}/etc/opensnitchd/default-config.json")));
 
     systemd = {
       packages = [ pkgs.opensnitch ];
@@ -171,9 +171,19 @@ in {
       ${concatMapStrings ({ file, local }: ''
         ln -sf '${file}' "${local}"
       '') rules}
+
+      if [ ! -f /etc/opensnitch-system-fw.json ]; then
+        cp "${pkgs.opensnitch}/etc/opensnitchd/system-fw.json" "/etc/opensnitchd/system-fw.json"
+      fi
     '');
 
-    environment.etc."opensnitchd/default-config.json".source = format.generate "default-config.json" cfg.settings;
+    environment.etc = mkMerge [ ({
+      "opensnitchd/default-config.json".source = format.generate "default-config.json" cfg.settings;
+    }) (mkIf (cfg.settings.ProcMonitorMethod == "ebpf") {
+      "opensnitchd/opensnitch.o".source = "${config.boot.kernelPackages.opensnitch-ebpf}/etc/opensnitchd/opensnitch.o";
+      "opensnitchd/opensnitch-dns.o".source = "${config.boot.kernelPackages.opensnitch-ebpf}/etc/opensnitchd/opensnitch-dns.o";
+      "opensnitchd/opensnitch-procs.o".source = "${config.boot.kernelPackages.opensnitch-ebpf}/etc/opensnitchd/opensnitch-procs.o";
+    })];
 
   };
 }
diff --git a/nixos/modules/services/tracing/tempo.nix b/nixos/modules/services/tracing/tempo.nix
index 4a098c31effeb..0b9ca2398b161 100644
--- a/nixos/modules/services/tracing/tempo.nix
+++ b/nixos/modules/services/tracing/tempo.nix
@@ -27,6 +27,18 @@ in {
         Specify a path to a configuration file that Tempo should use.
       '';
     };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = lib.literalExpression
+        ''
+          [ "-config.expand-env=true" ]
+        '';
+      description = lib.mdDoc ''
+        Additional flags to pass to the `ExecStart=` in `tempo.service`.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -54,7 +66,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${pkgs.tempo}/bin/tempo --config.file=${conf}";
+        ExecStart = "${pkgs.tempo}/bin/tempo --config.file=${conf} ${lib.escapeShellArgs cfg.extraFlags}";
         DynamicUser = true;
         Restart = "always";
         ProtectSystem = "full";
diff --git a/nixos/modules/services/video/mediamtx.nix b/nixos/modules/services/video/mediamtx.nix
index 18a9e3d5fe305..c3abd9cdcc5cb 100644
--- a/nixos/modules/services/video/mediamtx.nix
+++ b/nixos/modules/services/video/mediamtx.nix
@@ -1,79 +1,66 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.mediamtx;
-  package = pkgs.mediamtx;
   format = pkgs.formats.yaml {};
 in
 {
+  meta.maintainers = with lib.maintainers; [ fpletz ];
+
   options = {
     services.mediamtx = {
-      enable = mkEnableOption (lib.mdDoc "MediaMTX");
+      enable = lib.mkEnableOption (lib.mdDoc "MediaMTX");
 
-      settings = mkOption {
+      package = lib.mkPackageOptionMD pkgs "mediamtx" { };
+
+      settings = lib.mkOption {
         description = lib.mdDoc ''
-          Settings for MediaMTX.
-          Read more at <https://github.com/aler9/mediamtx/blob/main/mediamtx.yml>
+          Settings for MediaMTX. Refer to the defaults at
+          <https://github.com/bluenviron/mediamtx/blob/main/mediamtx.yml>.
         '';
         type = format.type;
-
-        default = {
-          logLevel = "info";
-          logDestinations = [
-            "stdout"
-          ];
-          # we set this so when the user uses it, it just works (see LogsDirectory below). but it's not used by default.
-          logFile = "/var/log/mediamtx/mediamtx.log";
-        };
-
+        default = {};
         example = {
           paths = {
             cam = {
-              runOnInit = "ffmpeg -f v4l2 -i /dev/video0 -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH";
+              runOnInit = "\${lib.getExe pkgs.ffmpeg} -f v4l2 -i /dev/video0 -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH";
               runOnInitRestart = true;
             };
           };
         };
       };
 
-      env = mkOption {
-        type = with types; attrsOf anything;
+      env = lib.mkOption {
+        type = with lib.types; attrsOf anything;
         description = lib.mdDoc "Extra environment variables for MediaMTX";
         default = {};
         example = {
           MTX_CONFKEY = "mykey";
         };
       };
+
+      allowVideoAccess = lib.mkEnableOption (lib.mdDoc ''
+        Enable access to video devices like cameras on the system.
+      '');
     };
   };
 
-  config = mkIf (cfg.enable) {
+  config = lib.mkIf cfg.enable {
     # NOTE: mediamtx watches this file and automatically reloads if it changes
     environment.etc."mediamtx.yaml".source = format.generate "mediamtx.yaml" cfg.settings;
 
     systemd.services.mediamtx = {
-      environment = cfg.env;
-
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      path = with pkgs; [
-        ffmpeg
-      ];
+      environment = cfg.env;
 
       serviceConfig = {
         DynamicUser = true;
         User = "mediamtx";
         Group = "mediamtx";
-
-        LogsDirectory = "mediamtx";
-
-        # user likely may want to stream cameras, can't hurt to add video group
-        SupplementaryGroups = "video";
-
-        ExecStart = "${package}/bin/mediamtx /etc/mediamtx.yaml";
+        SupplementaryGroups = lib.mkIf cfg.allowVideoAccess "video";
+        ExecStart = "${cfg.package}/bin/mediamtx /etc/mediamtx.yaml";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
index 688367cafaf55..b0d3c44cea81c 100644
--- a/nixos/modules/services/web-apps/grocy.nix
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -115,10 +115,8 @@ in {
       user = "grocy";
       group = "nginx";
 
-      # PHP 8.0 is the only version which is supported/tested by upstream:
-      # https://github.com/grocy/grocy/blob/v3.3.0/README.md#how-to-install
-      # Compatibility with PHP 8.1 is available on their development branch:
-      # https://github.com/grocy/grocy/commit/38a4ad8ec480c29a1bff057b3482fd103b036848
+      # PHP 8.1 is the only version which is supported/tested by upstream:
+      # https://github.com/grocy/grocy/blob/v4.0.0/README.md#platform-support
       phpPackage = pkgs.php81;
 
       inherit (cfg.phpfpm) settings;
@@ -168,7 +166,7 @@ in {
   };
 
   meta = {
-    maintainers = with maintainers; [ ma27 ];
+    maintainers = with maintainers; [ n0emis ];
     doc = ./grocy.md;
   };
 }
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 8823da0100141..5603ef7392e82 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -7,6 +7,9 @@ let
 
   settingsFile = settingsFormat.generate "invidious-settings" cfg.settings;
 
+  generatedHmacKeyFile = "/var/lib/invidious/hmac_key";
+  generateHmac = cfg.hmacKeyFile == null;
+
   serviceConfig = {
     systemd.services.invidious = {
       description = "Invidious (An alternative YouTube front-end)";
@@ -14,22 +17,47 @@ let
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      script =
-        let
-          jqFilter = "."
-            + lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\""
-            + " | .[0]"
-            + lib.optionalString (cfg.extraSettingsFile != null) " * .[1]";
-          jqFiles = [ settingsFile ] ++ lib.optional (cfg.extraSettingsFile != null) cfg.extraSettingsFile;
-        in
-        ''
-          export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${lib.escapeShellArgs jqFiles})"
-          exec ${cfg.package}/bin/invidious
-        '';
+      preStart = lib.optionalString generateHmac ''
+        if [[ ! -e "${generatedHmacKeyFile}" ]]; then
+          ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
+          chmod 0600 "${generatedHmacKeyFile}"
+        fi
+      '';
+
+      script = ''
+        configParts=()
+      ''
+      # autogenerated hmac_key
+      + lib.optionalString generateHmac ''
+        configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
+      ''
+      # generated settings file
+      + ''
+        configParts+=("$(< ${lib.escapeShellArg settingsFile})")
+      ''
+      # optional database password file
+      + lib.optionalString (cfg.database.host != null) ''
+        configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
+      ''
+      # optional extra settings file
+      + lib.optionalString (cfg.extraSettingsFile != null) ''
+        configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
+      ''
+      # explicitly specified hmac key file
+      + lib.optionalString (cfg.hmacKeyFile != null) ''
+        configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
+      ''
+      # merge all parts into a single configuration with later elements overriding previous elements
+      + ''
+        export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
+        exec ${cfg.package}/bin/invidious
+      '';
 
       serviceConfig = {
         RestartSec = "2s";
         DynamicUser = true;
+        StateDirectory = "invidious";
+        StateDirectoryMode = "0750";
 
         CapabilityBoundingSet = "";
         PrivateDevices = true;
@@ -171,6 +199,18 @@ in
       '';
     };
 
+    hmacKeyFile = lib.mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        A path to a file containing the `hmac_key`. If `null`, a key will be generated automatically on first
+        start.
+
+        If non-`null`, this option overrides any `hmac_key` specified in {option}`services.invidious.settings` or
+        via {option}`services.invidious.extraSettingsFile`.
+      '';
+    };
+
     extraSettingsFile = lib.mkOption {
       type = types.nullOr types.str;
       default = null;
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index 6dfba907fb5d5..895f3a9f1b4bb 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -160,6 +160,10 @@ in
               root * ${cfg.ui.package}/dist
               file_server
             }
+            handle_path /static/undefined/* {
+              root * ${cfg.ui.package}/dist
+              file_server
+            }
             @for_backend {
               path /api/* /pictrs/* /feeds/* /nodeinfo/*
             }
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index e2ef350ba4e5a..5f42f42a9af9c 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -1,7 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.netbox;
   pythonFmt = pkgs.formats.pythonVars {};
@@ -17,7 +15,7 @@ let
   pkg = (cfg.package.overrideAttrs (old: {
     installPhase = old.installPhase + ''
       ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
-    '' + optionalString cfg.enableLdap ''
+    '' + lib.optionalString cfg.enableLdap ''
       ln -s ${cfg.ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
     '';
   })).override {
@@ -31,7 +29,7 @@ let
 
 in {
   options.services.netbox = {
-    enable = mkOption {
+    enable = lib.mkOption {
       type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
@@ -66,18 +64,18 @@ in {
       };
     };
 
-    listenAddress = mkOption {
-      type = types.str;
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
       default = "[::1]";
       description = lib.mdDoc ''
         Address the server will listen on.
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
-      defaultText = literalExpression ''
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = if lib.versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+      defaultText = lib.literalExpression ''
         if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
       '';
       description = lib.mdDoc ''
@@ -85,18 +83,18 @@ in {
       '';
     };
 
-    port = mkOption {
-      type = types.port;
+    port = lib.mkOption {
+      type = lib.types.port;
       default = 8001;
       description = lib.mdDoc ''
         Port the server will listen on.
       '';
     };
 
-    plugins = mkOption {
-      type = types.functionTo (types.listOf types.package);
+    plugins = lib.mkOption {
+      type = with lib.types; functionTo (listOf package);
       default = _: [];
-      defaultText = literalExpression ''
+      defaultText = lib.literalExpression ''
         python3Packages: with python3Packages; [];
       '';
       description = lib.mdDoc ''
@@ -104,23 +102,23 @@ in {
       '';
     };
 
-    dataDir = mkOption {
-      type = types.str;
+    dataDir = lib.mkOption {
+      type = lib.types.str;
       default = "/var/lib/netbox";
       description = lib.mdDoc ''
         Storage path of netbox.
       '';
     };
 
-    secretKeyFile = mkOption {
-      type = types.path;
+    secretKeyFile = lib.mkOption {
+      type = lib.types.path;
       description = lib.mdDoc ''
         Path to a file containing the secret key.
       '';
     };
 
-    extraConfig = mkOption {
-      type = types.lines;
+    extraConfig = lib.mkOption {
+      type = lib.types.lines;
       default = "";
       description = lib.mdDoc ''
         Additional lines of configuration appended to the `configuration.py`.
@@ -128,8 +126,8 @@ in {
       '';
     };
 
-    enableLdap = mkOption {
-      type = types.bool;
+    enableLdap = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Enable LDAP-Authentication for Netbox.
@@ -138,8 +136,8 @@ in {
       '';
     };
 
-    ldapConfigPath = mkOption {
-      type = types.path;
+    ldapConfigPath = lib.mkOption {
+      type = lib.types.path;
       default = "";
       description = lib.mdDoc ''
         Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
@@ -173,15 +171,17 @@ in {
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     services.netbox = {
-      plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+      plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
       settings = {
         STATIC_ROOT = staticDir;
         MEDIA_ROOT = "${cfg.dataDir}/media";
         REPORTS_ROOT = "${cfg.dataDir}/reports";
         SCRIPTS_ROOT = "${cfg.dataDir}/scripts";
 
+        GIT_PATH = "${pkgs.gitMinimal}/bin/git";
+
         DATABASE = {
           NAME = "netbox";
           USER = "netbox";
@@ -264,40 +264,40 @@ in {
         RestartSec = 30;
       };
     in {
-      netbox-migration = {
-        description = "NetBox migrations";
-        wantedBy = [ "netbox.target" ];
-
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-
-        serviceConfig = defaultServiceConfig // {
-          Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/netbox migrate
-          '';
-          PrivateTmp = true;
-        };
-      };
-
       netbox = {
         description = "NetBox WSGI Service";
         documentation = [ "https://docs.netbox.dev/" ];
 
         wantedBy = [ "netbox.target" ];
 
-        after = [ "network-online.target" "netbox-migration.service" ];
+        after = [ "network-online.target" ];
         wants = [ "network-online.target" ];
 
+        environment.PYTHONPATH = pkg.pythonPath;
+
         preStart = ''
+          # On the first run, or on upgrade / downgrade, run migrations and related.
+          # This mostly correspond to upstream NetBox's 'upgrade.sh' script.
+          versionFile="${cfg.dataDir}/version"
+
+          if [[ -e "$versionFile" && "$(cat "$versionFile")" == "${cfg.package.version}" ]]; then
+            exit 0
+          fi
+
+          ${pkg}/bin/netbox migrate
           ${pkg}/bin/netbox trace_paths --no-input
           ${pkg}/bin/netbox collectstatic --no-input
           ${pkg}/bin/netbox remove_stale_contenttypes --no-input
+          # TODO: remove the condition when we remove netbox_3_3
+          ${lib.optionalString
+            (lib.versionAtLeast cfg.package.version "3.5.0")
+            "${pkg}/bin/netbox reindex --lazy"}
+          ${pkg}/bin/netbox clearsessions
+          ${pkg}/bin/netbox clearcache
+
+          echo "${cfg.package.version}" > "$versionFile"
         '';
 
-        environment.PYTHONPATH = pkg.pythonPath;
-
         serviceConfig = defaultServiceConfig // {
           ExecStart = ''
             ${pkgs.python3Packages.gunicorn}/bin/gunicorn netbox.wsgi \
@@ -331,7 +331,7 @@ in {
 
         wantedBy = [ "multi-user.target" ];
 
-        after = [ "network-online.target" ];
+        after = [ "network-online.target" "netbox.service" ];
         wants = [ "network-online.target" ];
 
         environment.PYTHONPATH = pkg.pythonPath;
@@ -351,7 +351,7 @@ in {
 
       wantedBy = [ "multi-user.target" ];
 
-      after = [ "network-online.target" ];
+      after = [ "network-online.target" "netbox.service" ];
       wants = [ "network-online.target" ];
 
       timerConfig = {
diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix
index d5ca6014780ab..423ad5375baab 100644
--- a/nixos/modules/services/web-apps/photoprism.nix
+++ b/nixos/modules/services/web-apps/photoprism.nix
@@ -123,7 +123,7 @@ in
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
+        SystemCallFilter = [ "@system-service" "~@setuid @keyring" ];
         UMask = "0066";
       } // lib.optionalAttrs (cfg.port < 1024) {
         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index 5cc9ef6dd6d98..cec0b379f67ae 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -24,21 +24,26 @@ let
         }
       '';
 
-  configFile =
-    let
-      Caddyfile = pkgs.writeTextDir "Caddyfile" ''
-        {
-          ${cfg.globalConfig}
-        }
-        ${cfg.extraConfig}
-      '';
+  settingsFormat = pkgs.formats.json { };
 
-      Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
-        mkdir -p $out
-        cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile
-        caddy fmt --overwrite $out/Caddyfile
-      '';
-    in
+  configFile =
+    if cfg.settings != { } then
+      settingsFormat.generate "caddy.json" cfg.settings
+    else
+      let
+        Caddyfile = pkgs.writeTextDir "Caddyfile" ''
+          {
+            ${cfg.globalConfig}
+          }
+          ${cfg.extraConfig}
+        '';
+
+        Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
+          mkdir -p $out
+          cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile
+          caddy fmt --overwrite $out/Caddyfile
+        '';
+      in
       "${if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile}/Caddyfile";
 
   etcConfigFile = "caddy/caddy_config";
@@ -299,6 +304,27 @@ in
         which could delay the reload essentially indefinitely.
       '';
     };
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      default = {};
+      description = lib.mdDoc ''
+        Structured configuration for Caddy to generate a Caddy JSON configuration file.
+        See <https://caddyserver.com/docs/json/> for available options.
+
+        ::: {.warning}
+        Using a [Caddyfile](https://caddyserver.com/docs/caddyfile) instead of a JSON config is highly recommended by upstream.
+        There are only very few exception to this.
+
+        Please use a Caddyfile via {option}`services.caddy.configFile`, {option}`services.caddy.virtualHosts` or
+        {option}`services.caddy.extraConfig` with {option}`services.caddy.globalConfig` instead.
+        :::
+
+        ::: {.note}
+        Takes presence over most `services.caddy.*` options, such as {option}`services.caddy.configFile` and {option}`services.caddy.virtualHosts`, if specified.
+        :::
+      '';
+    };
   };
 
   # implementation
diff --git a/nixos/modules/services/web-servers/rustus.nix b/nixos/modules/services/web-servers/rustus.nix
index 95c9a64555793..878d790e36667 100644
--- a/nixos/modules/services/web-servers/rustus.nix
+++ b/nixos/modules/services/web-servers/rustus.nix
@@ -226,6 +226,10 @@ in
       serviceConfig = {
         ExecStart = "${pkgs.rustus}/bin/rustus";
         StateDirectory = "rustus";
+        # User name is defined here to enable restoring a backup for example
+        # You will run the backup restore command as sudo -u rustus in order
+        # to have write permissions to /var/lib
+        User = "rustus";
         DynamicUser = true;
         LoadCredential = lib.optionals isHybridS3 [
           "S3_ACCESS_KEY_PATH:${cfg.storage.s3_access_key_file}"
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index a734bc288c9f3..bee627ec76c01 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -237,6 +237,11 @@ in {
       budgie.budgie-control-center
     ];
 
+    # Register packages for udev.
+    services.udev.packages = with pkgs; [
+      budgie.magpie
+    ];
+
     # Shell integration for MATE Terminal.
     programs.bash.vteIntegration = true;
     programs.zsh.vteIntegration = true;
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index b3cbe4c324df1..bb42c52b69ca4 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -212,10 +212,12 @@ in
       programs.bash.vteIntegration = mkDefault true;
       programs.zsh.vteIntegration = mkDefault true;
 
-      # Harmonize Qt applications under Cinnamon
-      qt.enable = true;
-      qt.platformTheme = "gnome";
-      qt.style = "adwaita";
+      # Qt application style
+      qt = {
+        enable = mkDefault true;
+        style = mkDefault "gtk2";
+        platformTheme = mkDefault "gtk2";
+      };
 
       # Default Fonts
       fonts.packages = with pkgs; [
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index d8a47e93afc74..eef7aa14057ea 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -200,7 +200,6 @@ in
         gtk3.out # for gtk-launch program
         onboard
         orca # elementary/greeter#668
-        qgnomeplatform
         sound-theme-freedesktop
         xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
       ]) ++ (with pkgs.pantheon; [
@@ -260,10 +259,10 @@ in
       programs.bash.vteIntegration = mkDefault true;
       programs.zsh.vteIntegration = mkDefault true;
 
-      # Harmonize Qt applications under Pantheon
-      qt.enable = true;
-      qt.platformTheme = "gnome";
-      qt.style = "adwaita";
+      # Use native GTK file chooser on Qt apps. This is because Qt does not know Pantheon.
+      # https://invent.kde.org/qt/qt/qtbase/-/blob/6.6/src/gui/platform/unix/qgenericunixthemes.cpp#L1312
+      # https://github.com/elementary/default-settings/blob/7.0.2/profile.d/qt-qpa-platformtheme.sh
+      environment.variables.QT_QPA_PLATFORMTHEME = mkDefault "gtk3";
 
       # Default Fonts
       fonts.packages = with pkgs; [
diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix
index 1d6f3daa40225..3df0ea9e60bb0 100644
--- a/nixos/modules/services/x11/picom.nix
+++ b/nixos/modules/services/x11/picom.nix
@@ -61,6 +61,8 @@ in {
       '';
     };
 
+    package = mkPackageOptionMD pkgs "picom" { };
+
     fade = mkOption {
       type = types.bool;
       default = false;
@@ -301,13 +303,13 @@ in {
       };
 
       serviceConfig = {
-        ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}";
+        ExecStart = "${getExe cfg.package} --config ${configFile}";
         RestartSec = 3;
         Restart = "always";
       };
     };
 
-    environment.systemPackages = [ pkgs.picom ];
+    environment.systemPackages = [ cfg.package ];
   };
 
   meta.maintainers = with lib.maintainers; [ rnhmjoj ];
diff --git a/nixos/modules/services/x11/window-managers/dwm.nix b/nixos/modules/services/x11/window-managers/dwm.nix
index 1881826944aac..e114f2e26b170 100644
--- a/nixos/modules/services/x11/window-managers/dwm.nix
+++ b/nixos/modules/services/x11/window-managers/dwm.nix
@@ -24,7 +24,7 @@ in
             patches = [
               (super.fetchpatch {
                 url = "https://dwm.suckless.org/patches/steam/dwm-steam-6.2.diff";
-                sha256 = "1ld1z3fh6p5f8gr62zknx3axsinraayzxw3rz1qwg73mx2zk5y1f";
+                sha256 = "sha256-f3lffBjz7+0Khyn9c9orzReoLTqBb/9gVGshYARGdVc=";
               })
             ];
           })
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index cfad640398689..04d90968c4c12 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -253,16 +253,24 @@ sub parse_systemd_ini {
 # If a directory with the same basename ending in .d exists next to the unit file, it will be
 # assumed to contain override files which will be parsed as well and handled properly.
 sub parse_unit {
-    my ($unit_path) = @_;
+    my ($unit_path, $base_unit_path) = @_;
 
     # Parse the main unit and all overrides
     my %unit_data;
     # Replace \ with \\ so glob() still works with units that have a \ in them
     # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\"
+    $base_unit_path =~ s/\\/\\\\/gmsx;
     $unit_path =~ s/\\/\\\\/gmsx;
-    foreach (glob("${unit_path}{,.d/*.conf}")) {
+
+    foreach (glob("${base_unit_path}{,.d/*.conf}")) {
         parse_systemd_ini(\%unit_data, "$_")
     }
+    # Handle drop-in template-unit instance overrides
+    if ($unit_path ne $base_unit_path) {
+        foreach (glob("${unit_path}.d/*.conf")) {
+            parse_systemd_ini(\%unit_data, "$_")
+        }
+    }
     return %unit_data;
 }
 
@@ -423,7 +431,7 @@ sub compare_units { ## no critic(Subroutines::ProhibitExcessComplexity)
 # Called when a unit exists in both the old systemd and the new system and the units
 # differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped.
 sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity)
-    my ($unit, $base_name, $new_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_;
+    my ($unit, $base_name, $new_unit_file, $new_base_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_;
 
     if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) {
         # Do nothing.  These cannot be restarted directly.
@@ -442,7 +450,7 @@ sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutin
         # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
         # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
     } else {
-        my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file);
+        my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file, $new_base_unit_file);
         if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) {
             $units_to_reload->{$unit} = 1;
             record_unit($reload_list_file, $unit);
@@ -538,31 +546,33 @@ my %units_to_filter; # units not shown
 
 my $active_cur = get_active_units();
 while (my ($unit, $state) = each(%{$active_cur})) {
-    my $base_unit = $unit;
+    my $cur_unit_file = "/etc/systemd/system/$unit";
+    my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
 
-    my $cur_unit_file = "/etc/systemd/system/$base_unit";
-    my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+    my $base_unit = $unit;
+    my $cur_base_unit_file = $cur_unit_file;
+    my $new_base_unit_file = $new_unit_file;
 
     # Detect template instances.
     if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
       $base_unit = "$1\@.$2";
-      $cur_unit_file = "/etc/systemd/system/$base_unit";
-      $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+      $cur_base_unit_file = "/etc/systemd/system/$base_unit";
+      $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
     }
 
     my $base_name = $base_unit;
     $base_name =~ s/\.[[:lower:]]*$//msx;
 
-    if (-e $cur_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) {
-        if (! -e $new_unit_file || abs_path($new_unit_file) eq "/dev/null") {
-            my %cur_unit_info = parse_unit($cur_unit_file);
+    if (-e $cur_base_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) {
+        if (! -e $new_base_unit_file || abs_path($new_base_unit_file) eq "/dev/null") {
+            my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file);
             if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) {
                 $units_to_stop{$unit} = 1;
             }
         }
 
         elsif ($unit =~ /\.target$/msx) {
-            my %new_unit_info = parse_unit($new_unit_file);
+            my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file);
 
             # Cause all active target units to be restarted below.
             # This should start most changed units we stop here as
@@ -596,11 +606,11 @@ while (my ($unit, $state) = each(%{$active_cur})) {
         }
 
         else {
-            my %cur_unit_info = parse_unit($cur_unit_file);
-            my %new_unit_info = parse_unit($new_unit_file);
+            my %cur_unit_info = parse_unit($cur_unit_file, $cur_base_unit_file);
+            my %new_unit_info = parse_unit($new_unit_file, $new_base_unit_file);
             my $diff = compare_units(\%cur_unit_info, \%new_unit_info);
             if ($diff == 1) {
-                handle_modified_unit($unit, $base_name, $new_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip);
+                handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip);
             } elsif ($diff == 2 and not $units_to_restart{$unit}) {
                 $units_to_reload{$unit} = 1;
                 record_unit($reload_list_file, $unit);
@@ -710,13 +720,14 @@ if ($action eq "dry-activate") {
     # Handle the activation script requesting the restart or reload of a unit.
     foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) {
         my $unit = $_;
+        my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
         my $base_unit = $unit;
-        my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+        my $new_base_unit_file = $new_unit_file;
 
         # Detect template instances.
         if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
           $base_unit = "$1\@.$2";
-          $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+          $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
         }
 
         my $base_name = $base_unit;
@@ -728,7 +739,7 @@ if ($action eq "dry-activate") {
             next;
         }
 
-        handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
+        handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
     }
     unlink($dry_restart_by_activation_file);
 
@@ -782,13 +793,14 @@ system("$out/activate", "$out") == 0 or $res = 2;
 # Handle the activation script requesting the restart or reload of a unit.
 foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) {
     my $unit = $_;
+    my $new_unit_file = "$toplevel/etc/systemd/system/$unit";
     my $base_unit = $unit;
-    my $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+    my $new_base_unit_file = $new_unit_file;
 
     # Detect template instances.
     if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
       $base_unit = "$1\@.$2";
-      $new_unit_file = "$toplevel/etc/systemd/system/$base_unit";
+      $new_base_unit_file = "$toplevel/etc/systemd/system/$base_unit";
     }
 
     my $base_name = $base_unit;
@@ -801,7 +813,7 @@ foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quie
         next;
     }
 
-    handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
+    handle_modified_unit($unit, $base_name, $new_unit_file, $new_base_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
 }
 # We can remove the file now because it has been propagated to the other restart/reload files
 unlink($restart_by_activation_file);
@@ -859,7 +871,7 @@ if (scalar(keys(%units_to_reload)) > 0) {
     for my $unit (keys(%units_to_reload)) {
         if (!unit_is_active($unit)) {
             # Figure out if we need to start the unit
-            my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit");
+            my %unit_info = parse_unit("$toplevel/etc/systemd/system/$unit", "$toplevel/etc/systemd/system/$unit");
             if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) {
                 $units_to_start{$unit} = 1;
                 record_unit($start_list_file, $unit);
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index a84e374624d1b..d1e7a0cb81785 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -516,38 +516,53 @@ sub addEntry {
     $conf .= "}\n\n";
 }
 
+sub addGeneration {
+    my ($name, $nameSuffix, $path, $options, $current) = @_;
 
-# Add default entries.
-$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
+    # Do not search for grand children
+    my @links = sort (glob "$path/specialisation/*");
 
-addEntry("@distroName@ - Default", $defaultConfig, $entryOptions, 1);
+    if ($current != 1 && scalar(@links) != 0) {
+        $conf .= "submenu \"> $name$nameSuffix\" --class submenu {\n";
+    }
 
-$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
+    addEntry("$name" . (scalar(@links) == 0 ? "" : " - Default") . $nameSuffix, $path, $options, $current);
 
-# Find all the children of the current default configuration
-# Do not search for grand children
-my @links = sort (glob "$defaultConfig/specialisation/*");
-foreach my $link (@links) {
+    # Find all the children of the current default configuration
+    # Do not search for grand children
+    foreach my $link (@links) {
 
-    my $entryName = "";
+        my $entryName = "";
 
-    my $cfgName = readFile("$link/configuration-name");
+        my $cfgName = readFile("$link/configuration-name");
 
-    my $date = strftime("%F", localtime(lstat($link)->mtime));
-    my $version =
-        -e "$link/nixos-version"
-        ? readFile("$link/nixos-version")
-        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+        my $date = strftime("%F", localtime(lstat($link)->mtime));
+        my $version =
+            -e "$link/nixos-version"
+            ? readFile("$link/nixos-version")
+            : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
 
-    if ($cfgName) {
-        $entryName = $cfgName;
-    } else {
-        my $linkname = basename($link);
-        $entryName = "($linkname - $date - $version)";
+        if ($cfgName) {
+            $entryName = $cfgName;
+        } else {
+            my $linkname = basename($link);
+            $entryName = "($linkname - $date - $version)";
+        }
+        addEntry("$name - $entryName", $link, "", 1);
+    }
+
+    if ($current != 1 && scalar(@links) != 0) {
+        $conf .= "}\n";
     }
-    addEntry("@distroName@ - $entryName", $link, "", 1);
 }
 
+# Add default entries.
+$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
+
+addGeneration("@distroName@", "", $defaultConfig, $entryOptions, 1);
+
+$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
+
 my $grubBootPath = $grubBoot->path;
 # extraEntries could refer to @bootRoot@, which we have to substitute
 $conf =~ s/\@bootRoot\@/$grubBootPath/g;
@@ -577,7 +592,7 @@ sub addProfile {
             -e "$link/nixos-version"
             ? readFile("$link/nixos-version")
             : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
-        addEntry("@distroName@ - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions, 0);
+        addGeneration("@distroName@ - Configuration " . nrFromGen($link), " ($date - $version)", $link, $subEntryOptions, 0);
     }
 
     $conf .= "}\n";
diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
index ee969e9bff5bf..8e68431ac571a 100644
--- a/nixos/modules/system/boot/loader/grub/memtest.nix
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -1,12 +1,10 @@
-# This module adds Memtest86+/Memtest86 to the GRUB boot menu.
-
+# This module adds Memtest86+ to the GRUB boot menu.
 { config, lib, pkgs, ... }:
 
 with lib;
 
 let
   memtest86 = pkgs.memtest86plus;
-  efiSupport = config.boot.loader.grub.efiSupport;
   cfg = config.boot.loader.grub.memtest86;
 in
 
@@ -19,11 +17,8 @@ in
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Make Memtest86+ (or MemTest86 if EFI support is enabled),
-          a memory testing program, available from the
-          GRUB boot menu. MemTest86 is an unfree program, so
-          this requires `allowUnfree` to be set to
-          `true`.
+          Make Memtest86+, a memory testing program, available from the GRUB
+          boot menu.
         '';
       };
 
@@ -63,34 +58,12 @@ in
     };
   };
 
-  config = mkMerge [
-    (mkIf (cfg.enable && efiSupport) {
-      assertions = [
-        {
-          assertion = cfg.params == [];
-          message = "Parameters are not available for MemTest86";
-        }
-      ];
-
-      boot.loader.grub.extraFiles = {
-        "memtest86.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
-      };
-
-      boot.loader.grub.extraEntries = ''
-        menuentry "Memtest86" {
-          chainloader /memtest86.efi
-        }
-      '';
-    })
-
-    (mkIf (cfg.enable && !efiSupport) {
-      boot.loader.grub.extraEntries = ''
-        menuentry "Memtest86+" {
-          linux16 @bootRoot@/memtest.bin ${toString cfg.params}
-        }
-      '';
-
-      boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
-    })
-  ];
+  config = mkIf cfg.enable {
+    boot.loader.grub.extraEntries = ''
+      menuentry "Memtest86+" {
+        linux @bootRoot@/memtest.bin ${toString cfg.params}
+      }
+    '';
+    boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
+  };
 }
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 8a3e89e5888bc..1770f0759434d 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -32,7 +32,7 @@ let
 
     inherit (config.system.nixos) distroName;
 
-    memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86-efi;
+    memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86plus;
 
     netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi;
 
@@ -147,10 +147,8 @@ in {
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Make MemTest86 available from the systemd-boot menu. MemTest86 is a
-          program for testing memory.  MemTest86 is an unfree program, so
-          this requires `allowUnfree` to be set to
-          `true`.
+          Make MemTest86+ available from the systemd-boot menu. MemTest86+ is a
+          program for testing memory.
         '';
       };
 
@@ -193,8 +191,8 @@ in {
       default = {};
       example = literalExpression ''
         { "memtest86.conf" = '''
-          title MemTest86
-          efi /efi/memtest86/memtest86.efi
+          title MemTest86+
+          efi /efi/memtest86/memtest.efi
         '''; }
       '';
       description = lib.mdDoc ''
@@ -213,7 +211,7 @@ in {
       type = types.attrsOf types.path;
       default = {};
       example = literalExpression ''
-        { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
+        { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; }
       '';
       description = lib.mdDoc ''
         A set of files to be copied to {file}`/boot`.
@@ -276,11 +274,8 @@ in {
     boot.loader.supportsInitrdSecrets = true;
 
     boot.loader.systemd-boot.extraFiles = mkMerge [
-      # TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
-      # be updated to use the 32-bit EFI app on 32-bit systems.  The 32-bit EFI
-      # app filename is BOOTIA32.efi.
       (mkIf cfg.memtest86.enable {
-        "efi/memtest86/BOOTX64.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
+        "efi/memtest86/memtest.efi" = "${pkgs.memtest86plus.efi}";
       })
       (mkIf cfg.netbootxyz.enable {
         "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
@@ -291,7 +286,7 @@ in {
       (mkIf cfg.memtest86.enable {
         "${cfg.memtest86.entryFilename}" = ''
           title  MemTest86
-          efi    /efi/memtest86/BOOTX64.efi
+          efi    /efi/memtest86/memtest.efi
         '';
       })
       (mkIf cfg.netbootxyz.enable {
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index 20f47a76c87b9..6fe460316091b 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -236,8 +236,8 @@ in
       };
 
       assertions = [
-        { assertion = cfg.enableNvidia -> config.hardware.opengl.driSupport32Bit or false;
-          message = "Option enableNvidia requires 32bit support libraries";
+        { assertion = cfg.enableNvidia && pkgs.stdenv.isx86_64 -> config.hardware.opengl.driSupport32Bit or false;
+          message = "Option enableNvidia on x86_64 requires 32bit support libraries";
         }];
 
       virtualisation.docker.daemon.settings = {
diff --git a/nixos/modules/virtualisation/lxd-agent.nix b/nixos/modules/virtualisation/lxd-agent.nix
new file mode 100644
index 0000000000000..5bcc86e3bcbe9
--- /dev/null
+++ b/nixos/modules/virtualisation/lxd-agent.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.virtualisation.lxd.agent;
+
+  # the lxd agent is provided by the lxd daemon through a virtiofs or 9p mount
+  # this is a port of the distrobuilder lxd-agent generator
+  # https://github.com/lxc/distrobuilder/blob/f77300bf7d7d5707b08eaf8a434d647d1ba81b5d/generators/lxd-agent.go#L18-L55
+  preStartScript = ''
+    PREFIX="/run/lxd_agent"
+
+    mount_virtiofs() {
+        mount -t virtiofs config "$PREFIX/.mnt" >/dev/null 2>&1
+    }
+
+    mount_9p() {
+        modprobe 9pnet_virtio >/dev/null 2>&1 || true
+        mount -t 9p config "$PREFIX/.mnt" -o access=0,trans=virtio,size=1048576 >/dev/null 2>&1
+    }
+
+    fail() {
+        umount -l "$PREFIX" >/dev/null 2>&1 || true
+        rmdir "$PREFIX" >/dev/null 2>&1 || true
+        echo "$1"
+        exit 1
+    }
+
+    # Setup the mount target.
+    umount -l "$PREFIX" >/dev/null 2>&1 || true
+    mkdir -p "$PREFIX"
+    mount -t tmpfs tmpfs "$PREFIX" -o mode=0700,size=50M
+    mkdir -p "$PREFIX/.mnt"
+
+    # Try virtiofs first.
+    mount_virtiofs || mount_9p || fail "Couldn't mount virtiofs or 9p, failing."
+
+    # Copy the data.
+    cp -Ra "$PREFIX/.mnt/"* "$PREFIX"
+
+    # Unmount the temporary mount.
+    umount "$PREFIX/.mnt"
+    rmdir "$PREFIX/.mnt"
+
+    # Fix up permissions.
+    chown -R root:root "$PREFIX"
+  '';
+in {
+  meta.maintainers = with lib.maintainers; [ adamcstephens ];
+
+  options = {
+    virtualisation.lxd.agent.enable = lib.mkEnableOption (lib.mdDoc "Enable LXD agent");
+  };
+
+  config = lib.mkIf cfg.enable {
+    # https://github.com/lxc/distrobuilder/blob/f77300bf7d7d5707b08eaf8a434d647d1ba81b5d/generators/lxd-agent.go#L108-L125
+    systemd.services.lxd-agent = {
+      enable = true;
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.kmod pkgs.util-linux ];
+
+      preStart = preStartScript;
+
+      # avoid killing nixos-rebuild switch when executed through lxc exec
+      stopIfChanged = false;
+
+      unitConfig = {
+        Description = "LXD - agent";
+        Documentation = "https://documentation.ubuntu.com/lxd/en/latest";
+        ConditionPathExists = "/dev/virtio-ports/org.linuxcontainers.lxd";
+        Before = lib.optionals config.services.cloud-init.enable [ "cloud-init.target" "cloud-init.service" "cloud-init-local.service" ];
+        DefaultDependencies = "no";
+        StartLimitInterval = "60";
+        StartLimitBurst = "10";
+      };
+
+      serviceConfig = {
+        Type = "notify";
+        WorkingDirectory = "-/run/lxd_agent";
+        ExecStart = "/run/lxd_agent/lxd-agent";
+        Restart = "on-failure";
+        RestartSec = "5s";
+      };
+    };
+
+    systemd.paths.lxd-agent = {
+      enable = true;
+      wantedBy = [ "multi-user.target" ];
+      pathConfig.PathExists = "/dev/virtio-ports/org.linuxcontainers.lxd";
+    };
+  };
+}
diff --git a/nixos/release.nix b/nixos/release.nix
index 93ebe000fc001..6da6faab73bed 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -143,7 +143,7 @@ in rec {
   manualHTML = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualHTML);
   manual = manualHTML; # TODO(@oxij): remove eventually
   manualEpub = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualEpub));
-  manpages = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manpages);
+  nixos-configuration-reference-manpage = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.nixos-configuration-reference-manpage);
   options = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.optionsJSON)).x86_64-linux;
 
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 530447b997863..e07dc54b7981e 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -341,6 +341,7 @@ in {
   hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
   hbase_2_4 = handleTest ./hbase.nix { package=pkgs.hbase_2_4; };
   hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
+  hddfancontrol = handleTest ./hddfancontrol.nix {};
   hedgedoc = handleTest ./hedgedoc.nix {};
   herbstluftwm = handleTest ./herbstluftwm.nix {};
   homepage-dashboard = handleTest ./homepage-dashboard.nix {};
@@ -367,6 +368,7 @@ in {
   iftop = handleTest ./iftop.nix {};
   incron = handleTest ./incron.nix {};
   influxdb = handleTest ./influxdb.nix {};
+  influxdb2 = handleTest ./influxdb2.nix {};
   initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
   initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix {};
@@ -390,6 +392,7 @@ in {
   jibri = handleTest ./jibri.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
+  jool = handleTest ./jool.nix {};
   k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
@@ -463,6 +466,7 @@ in {
   matrix-conduit = handleTest ./matrix/conduit.nix {};
   matrix-synapse = handleTest ./matrix/synapse.nix {};
   mattermost = handleTest ./mattermost.nix {};
+  mediamtx = handleTest ./mediamtx.nix {};
   mediatomb = handleTest ./mediatomb.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   meilisearch = handleTest ./meilisearch.nix {};
@@ -522,6 +526,7 @@ in {
   networking.scripted = handleTest ./networking.nix { networkd = false; };
   netbox = handleTest ./web-apps/netbox.nix { inherit (pkgs) netbox; };
   netbox_3_3 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_3; };
+  netbox-upgrade = handleTest ./web-apps/netbox-upgrade.nix {};
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
@@ -571,6 +576,7 @@ in {
   openresty-lua = handleTest ./openresty-lua.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
   opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
+  opensnitch = handleTest ./opensnitch.nix {};
   openssh = handleTest ./openssh.nix {};
   octoprint = handleTest ./octoprint.nix {};
   openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
diff --git a/nixos/tests/budgie.nix b/nixos/tests/budgie.nix
index 34ee1e303de9f..19d9b2bd0bede 100644
--- a/nixos/tests/budgie.nix
+++ b/nixos/tests/budgie.nix
@@ -52,10 +52,16 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           machine.wait_for_window("budgie-daemon")
           machine.wait_until_succeeds("pgrep budgie-panel")
           machine.wait_for_window("budgie-panel")
+          # We don't check xwininfo for this one.
+          # See https://github.com/NixOS/nixpkgs/pull/216737#discussion_r1155312754
+          machine.wait_until_succeeds("pgrep budgie-wm")
 
       with subtest("Open MATE terminal"):
           machine.succeed("su - ${user.name} -c 'DISPLAY=:0 mate-terminal >&2 &'")
           machine.wait_for_window("Terminal")
+
+      with subtest("Check if budgie-wm has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep budgie-wm")
           machine.sleep(20)
           machine.screenshot("screen")
     '';
diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix
index 238091ec606f5..5a0d3539394b6 100644
--- a/nixos/tests/caddy.nix
+++ b/nixos/tests/caddy.nix
@@ -34,6 +34,20 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           "http://localhost:8081" = { };
         };
       };
+      specialisation.rfc42.configuration = {
+        services.caddy.settings = {
+          apps.http.servers.default = {
+            listen = [ ":80" ];
+            routes = [{
+              handle = [{
+                body = "hello world";
+                handler = "static_response";
+                status_code = 200;
+              }];
+            }];
+          };
+        };
+      };
     };
   };
 
@@ -41,6 +55,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     let
       justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload";
       multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs";
+      rfc42Config = "${nodes.webserver.system.build.toplevel}/specialisation/rfc42";
     in
     ''
       url = "http://localhost/example.html"
@@ -62,5 +77,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           )
           webserver.wait_for_open_port(8080)
           webserver.wait_for_open_port(8081)
+
+      with subtest("rfc42 settings config"):
+          webserver.succeed(
+              "${rfc42Config}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(80)
+          webserver.succeed("curl http://localhost | grep hello")
     '';
 })
diff --git a/nixos/tests/fcitx5/config b/nixos/tests/fcitx5/config
deleted file mode 100644
index cf4334639f1c0..0000000000000
--- a/nixos/tests/fcitx5/config
+++ /dev/null
@@ -1,11 +0,0 @@
-[Hotkey]
-EnumerateSkipFirst=False
-
-[Hotkey/TriggerKeys]
-0=Control+space
-
-[Hotkey/EnumerateForwardKeys]
-0=Alt+Shift_L
-
-[Hotkey/EnumerateBackwardKeys]
-0=Alt+Shift_R
diff --git a/nixos/tests/fcitx5/default.nix b/nixos/tests/fcitx5/default.nix
index 9b000da48eaf2..c113f2e2c052c 100644
--- a/nixos/tests/fcitx5/default.nix
+++ b/nixos/tests/fcitx5/default.nix
@@ -36,6 +36,43 @@ rec {
         pkgs.fcitx5-m17n
         pkgs.fcitx5-mozc
       ];
+      fcitx5.settings = {
+        globalOptions = {
+          "Hotkey"."EnumerateSkipFirst" = "False";
+          "Hotkey/TriggerKeys"."0" = "Control+space";
+          "Hotkey/EnumerateForwardKeys"."0" = "Alt+Shift_L";
+          "Hotkey/EnumerateBackwardKeys"."0" = "Alt+Shift_R";
+        };
+        inputMethod = {
+          "GroupOrder" = {
+            "0" = "NixOS_test";
+          };
+          "Groups/0" = {
+            "Default Layout" = "us";
+            "DefaultIM" = "wbx";
+            "Name" = "NixOS_test";
+          };
+          "Groups/0/Items/0" = {
+            "Name" = "keyboard-us";
+          };
+          "Groups/0/Items/1" = {
+            "Layout" = "us";
+            "Name" = "wbx";
+          };
+          "Groups/0/Items/2" = {
+            "Layout" = "us";
+            "Name" = "hangul";
+          };
+          "Groups/0/Items/3" = {
+            "Layout" = "us";
+            "Name" = "m17n_sa_harvard-kyoto";
+          };
+          "Groups/0/Items/4" = {
+            "Layout" = "us";
+            "Name" = "mozc";
+          };
+        };
+      };
     };
   };
 
@@ -43,7 +80,6 @@ rec {
     let
       user = nodes.machine.users.users.alice;
       xauth         = "${user.home}/.Xauthority";
-      fcitx_confdir = "${user.home}/.config/fcitx5";
     in
       ''
             start_all()
@@ -56,15 +92,6 @@ rec {
             machine.succeed("su - ${user.name} -c 'kill $(pgrep fcitx5)'")
             machine.sleep(1)
 
-            machine.copy_from_host(
-                "${./profile}",
-                "${fcitx_confdir}/profile",
-            )
-            machine.copy_from_host(
-                "${./config}",
-                "${fcitx_confdir}/config",
-            )
-
             machine.succeed("su - ${user.name} -c 'alacritty >&2 &'")
             machine.succeed("su - ${user.name} -c 'fcitx5 >&2 &'")
             machine.sleep(10)
diff --git a/nixos/tests/fcitx5/profile b/nixos/tests/fcitx5/profile
deleted file mode 100644
index 1b48c634e0eba..0000000000000
--- a/nixos/tests/fcitx5/profile
+++ /dev/null
@@ -1,27 +0,0 @@
-[Groups/0]
-Name=NixOS_test
-Default Layout=us
-DefaultIM=wbx
-
-[Groups/0/Items/0]
-Name=keyboard-us
-Layout=
-
-[Groups/0/Items/1]
-Name=wbx
-Layout=us
-
-[Groups/0/Items/2]
-Name=hangul
-Layout=us
-
-[Groups/0/Items/3]
-Name=m17n_sa_harvard-kyoto
-Layout=us
-
-[Groups/0/Items/4]
-Name=mozc
-Layout=us
-
-[GroupOrder]
-0=NixOS_test
diff --git a/nixos/tests/hddfancontrol.nix b/nixos/tests/hddfancontrol.nix
new file mode 100644
index 0000000000000..b5fa7ccb2c19b
--- /dev/null
+++ b/nixos/tests/hddfancontrol.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "hddfancontrol";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ benley ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    services.hddfancontrol.enable = true;
+    services.hddfancontrol.disks = ["/dev/vda"];
+    services.hddfancontrol.pwmPaths = ["/test/hwmon1/pwm1"];
+    services.hddfancontrol.extraArgs = ["--pwm-start-value=32"
+                                        "--pwm-stop-value=0"];
+
+    systemd.services.hddfancontrol_fixtures = {
+      description = "Install test fixtures for hddfancontrol";
+      serviceConfig = {
+        Type = "oneshot";
+      };
+      script = ''
+        mkdir -p /test/hwmon1
+        echo 255 > /test/hwmon1/pwm1
+        echo 2 > /test/hwmon1/pwm1_enable
+      '';
+      wantedBy = ["hddfancontrol.service"];
+      before = ["hddfancontrol.service"];
+    };
+
+    systemd.services.hddfancontrol.serviceConfig.ReadWritePaths = "/test";
+  };
+
+  # hddfancontrol.service will fail to start because qemu /dev/vda doesn't have
+  # any thermal interfaces, but it should ensure that fans appear to be running
+  # before it aborts.
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("journalctl -eu hddfancontrol.service|grep 'Setting fan speed'")
+    machine.shutdown()
+
+  '';
+
+})
diff --git a/nixos/tests/influxdb2.nix b/nixos/tests/influxdb2.nix
new file mode 100644
index 0000000000000..1631ac1d94081
--- /dev/null
+++ b/nixos/tests/influxdb2.nix
@@ -0,0 +1,225 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "influxdb2";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    environment.systemPackages = [ pkgs.influxdb2-cli ];
+    # Make sure that the service is restarted immediately if tokens need to be rewritten
+    # without relying on any Restart=on-failure behavior
+    systemd.services.influxdb2.serviceConfig.RestartSec = 6000;
+    services.influxdb2.enable = true;
+    services.influxdb2.provision = {
+      enable = true;
+      initialSetup = {
+        organization = "default";
+        bucket = "default";
+        passwordFile = pkgs.writeText "admin-pw" "ExAmPl3PA55W0rD";
+        tokenFile = pkgs.writeText "admin-token" "verysecureadmintoken";
+      };
+      organizations.someorg = {
+        buckets.somebucket = {};
+        auths.sometoken = {
+          description = "some auth token";
+          readBuckets = ["somebucket"];
+          writeBuckets = ["somebucket"];
+        };
+      };
+      users.someuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
+    };
+
+    specialisation.withModifications.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.someorg.buckets.somebucket.present = false;
+        organizations.someorg.auths.sometoken.present = false;
+        users.someuser.present = false;
+
+        organizations.myorg = {
+          description = "Myorg description";
+          buckets.mybucket = {
+            description = "Mybucket description";
+          };
+          auths.mytoken = {
+            operator = true;
+            description = "operator token";
+            tokenFile = pkgs.writeText "tmp-tok" "someusertoken";
+          };
+        };
+        users.myuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
+      };
+    };
+
+    specialisation.withParentDelete.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.someorg.present = false;
+        # Deleting the parent implies:
+        #organizations.someorg.buckets.somebucket.present = false;
+        #organizations.someorg.auths.sometoken.present = false;
+      };
+    };
+
+    specialisation.withNewTokens.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.default = {
+          auths.operator = {
+            operator = true;
+            description = "new optoken";
+            tokenFile = pkgs.writeText "tmp-tok" "newoptoken";
+          };
+          auths.allaccess = {
+            operator = true;
+            description = "new allaccess";
+            tokenFile = pkgs.writeText "tmp-tok" "newallaccess";
+          };
+          auths.specifics = {
+            description = "new specifics";
+            readPermissions = ["users" "tasks"];
+            writePermissions = ["tasks"];
+            tokenFile = pkgs.writeText "tmp-tok" "newspecificstoken";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+      tokenArg = "--token verysecureadmintoken";
+    in ''
+      def assert_contains(haystack, needle):
+          if needle not in haystack:
+              print("The haystack that will cause the following exception is:")
+              print("---")
+              print(haystack)
+              print("---")
+              raise Exception(f"Expected string '{needle}' was not found")
+
+      def assert_lacks(haystack, needle):
+          if needle in haystack:
+              print("The haystack that will cause the following exception is:")
+              print("---")
+              print(haystack, end="")
+              print("---")
+              raise Exception(f"Unexpected string '{needle}' was found")
+
+      machine.wait_for_unit("influxdb2.service")
+
+      machine.fail("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:wrongpassword")
+      machine.succeed("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:ExAmPl3PA55W0rD")
+
+      out = machine.succeed("influx org list ${tokenArg}")
+      assert_contains(out, "default")
+      assert_lacks(out, "myorg")
+      assert_contains(out, "someorg")
+
+      out = machine.succeed("influx bucket list ${tokenArg} --org default")
+      assert_contains(out, "default")
+
+      machine.fail("influx bucket list ${tokenArg} --org myorg")
+
+      out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+      assert_contains(out, "somebucket")
+
+      out = machine.succeed("influx user list ${tokenArg}")
+      assert_contains(out, "admin")
+      assert_lacks(out, "myuser")
+      assert_contains(out, "someuser")
+
+      out = machine.succeed("influx auth list ${tokenArg}")
+      assert_lacks(out, "operator token")
+      assert_contains(out, "some auth token")
+
+      with subtest("withModifications"):
+        machine.succeed('${specialisations}/withModifications/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_contains(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+        assert_lacks(out, "somebucket")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_lacks(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_lacks(out, "some auth token")
+
+        # Make sure the user token is also usable
+        machine.succeed("influx auth list --token someusertoken")
+
+      with subtest("keepsUnrelated"):
+        machine.succeed('${nodes.machine.system.build.toplevel}/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_contains(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org default")
+        assert_contains(out, "default")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+        assert_contains(out, "somebucket")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_contains(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_contains(out, "some auth token")
+
+      with subtest("withParentDelete"):
+        machine.succeed('${specialisations}/withParentDelete/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_lacks(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org default")
+        assert_contains(out, "default")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        machine.fail("influx bucket list ${tokenArg} --org someorg")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_contains(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_lacks(out, "some auth token")
+
+      with subtest("withNewTokens"):
+        machine.succeed('${specialisations}/withNewTokens/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_contains(out, "some auth token")
+        assert_contains(out, "new optoken")
+        assert_contains(out, "new allaccess")
+        assert_contains(out, "new specifics")
+    '';
+})
diff --git a/nixos/tests/jool.nix b/nixos/tests/jool.nix
new file mode 100644
index 0000000000000..6d5ded9b18e07
--- /dev/null
+++ b/nixos/tests/jool.nix
@@ -0,0 +1,250 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+
+  ipv6Only = {
+    networking.useDHCP = false;
+    networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ];
+  };
+
+  ipv4Only = {
+    networking.useDHCP = false;
+    networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ];
+  };
+
+  webserver = ip: msg: {
+    systemd.services.webserver = {
+      description = "Mock webserver";
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Restart = "always";
+      script = ''
+        while true; do
+        {
+          printf 'HTTP/1.0 200 OK\n'
+          printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n'
+          printf '\n${msg}\n\n'
+        } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80
+        done
+      '';
+    };
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
+in
+
+{
+  siit = makeTest {
+    # This test simulates the setup described in [1] with two IPv6 and
+    # IPv4-only devices on different subnets communicating through a border
+    # relay running Jool in SIIT mode.
+    # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html
+    name = "jool-siit";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    # Border relay
+    nodes.relay = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ];
+      virtualisation.vlans = [ 1 2 ];
+
+      # Enable packet routing
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = 1;
+        "net.ipv4.conf.all.forwarding" = 1;
+      };
+
+      networking.useDHCP = false;
+      networking.interfaces = lib.mkVMOverride {
+        eth1.ipv6.addresses = [ { address = "fd::198.51.100.1"; prefixLength = 120; } ];
+        eth2.ipv4.addresses = [ { address = "192.0.2.1";  prefixLength = 24; } ];
+      };
+
+      networking.jool = {
+        enable = true;
+        siit.enable = true;
+        siit.config.global.pool6 = "fd::/96";
+      };
+    };
+
+    # IPv6 only node
+    nodes.alice = { ... }: {
+      imports = [
+        ../modules/profiles/minimal.nix
+        ipv6Only
+        (webserver 6 "Hello, Bob!")
+      ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "fd::198.51.100.8"; prefixLength = 120; } ];
+        routes    = [ { address = "fd::192.0.2.0"; prefixLength = 120;
+                        via = "fd::198.51.100.1"; } ];
+      };
+    };
+
+    # IPv4 only node
+    nodes.bob = { ... }: {
+      imports = [
+        ../modules/profiles/minimal.nix
+        ipv4Only
+        (webserver 4 "Hello, Alice!")
+      ];
+
+      virtualisation.vlans = [ 2 ];
+      networking.interfaces.eth1.ipv4 = {
+        addresses = [ { address = "192.0.2.16"; prefixLength = 24; } ];
+        routes    = [ { address = "198.51.100.0"; prefixLength = 24;
+                        via = "192.0.2.1"; } ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      relay.wait_for_unit("jool-siit.service")
+      alice.wait_for_unit("network-addresses-eth1.service")
+      bob.wait_for_unit("network-addresses-eth1.service")
+
+      with subtest("Alice and Bob can't ping each other"):
+        relay.systemctl("stop jool-siit.service")
+        alice.fail("ping -c1 fd::192.0.2.16")
+        bob.fail("ping -c1 198.51.100.8")
+
+      with subtest("Alice and Bob can ping using the relay"):
+        relay.systemctl("start jool-siit.service")
+        alice.wait_until_succeeds("ping -c1 fd::192.0.2.16")
+        bob.wait_until_succeeds("ping -c1 198.51.100.8")
+
+      with subtest("Alice can connect to Bob's webserver"):
+        bob.wait_for_open_port(80)
+        alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2")
+        alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice")
+
+      with subtest("Bob can connect to Alices's webserver"):
+        alice.wait_for_open_port(80)
+        bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob")
+    '';
+  };
+
+  nat64 = makeTest {
+    # This test simulates the setup described in [1] with two IPv6-only nodes
+    # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN.
+    # The router runs Jool in stateful NAT64 mode, masquarading the LAN and
+    # forwarding ports using static BIB entries.
+    # [1]: https://nicmx.github.io/Jool/en/run-nat64.html
+    name = "jool-nat64";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    # Router
+    nodes.router = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ];
+      virtualisation.vlans = [ 1 2 ];
+
+      # Enable packet routing
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = 1;
+        "net.ipv4.conf.all.forwarding" = 1;
+      };
+
+      networking.useDHCP = false;
+      networking.interfaces = lib.mkVMOverride {
+        eth1.ipv6.addresses = [ { address = "2001:db8::1"; prefixLength = 96; } ];
+        eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ];
+      };
+
+      networking.jool = {
+        enable = true;
+        nat64.enable = true;
+        nat64.config = {
+          bib = [
+            { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver)
+              "protocol"     = "TCP";
+              "ipv4 address" = "203.0.113.1#80";
+              "ipv6 address" = "2001:db8::9#80";
+            }
+          ];
+          pool4 = [
+            # Ports for dynamic translation
+            { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+            { protocol =  "UDP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+            { protocol = "ICMP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+            # Ports for static BIB entries
+            { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "80"; }
+          ];
+        };
+      };
+    };
+
+    # LAN client (IPv6 only)
+    nodes.client = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ipv6Only ];
+      virtualisation.vlans = [ 1 ];
+
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ];
+        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
+                        via = "2001:db8::1"; } ];
+      };
+    };
+
+    # LAN server (IPv6 only)
+    nodes.homeserver = { ... }: {
+      imports = [
+        ../modules/profiles/minimal.nix
+        ipv6Only
+        (webserver 6 "Hello from IPv6!")
+      ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ];
+        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
+                        via = "2001:db8::1"; } ];
+      };
+    };
+
+    # WAN server (IPv4 only)
+    nodes.server = { ... }: {
+      imports = [
+        ../modules/profiles/minimal.nix
+        ipv4Only
+        (webserver 4 "Hello from IPv4!")
+      ];
+
+      virtualisation.vlans = [ 2 ];
+      networking.interfaces.eth1.ipv4.addresses =
+        [ { address = "203.0.113.16"; prefixLength = 24; } ];
+    };
+
+    testScript = ''
+      start_all()
+
+      for node in [client, homeserver, server]:
+        node.wait_for_unit("network-addresses-eth1.service")
+
+      with subtest("Client can ping the WAN server"):
+        router.wait_for_unit("jool-nat64.service")
+        client.succeed("ping -c1 64:ff9b::203.0.113.16")
+
+      with subtest("Client can connect to the WAN webserver"):
+        server.wait_for_open_port(80)
+        client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!")
+
+      with subtest("Router BIB entries are correctly populated"):
+        router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'")
+        router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'")
+
+      with subtest("WAN server can reach the LAN server"):
+        homeserver.wait_for_open_port(80)
+        server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!")
+    '';
+
+  };
+
+}
diff --git a/nixos/tests/matrix/synapse.nix b/nixos/tests/matrix/synapse.nix
index 698d67c793e3a..98b0774691923 100644
--- a/nixos/tests/matrix/synapse.nix
+++ b/nixos/tests/matrix/synapse.nix
@@ -65,7 +65,7 @@ in {
 
   nodes = {
     # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
-    serverpostgres = { pkgs, nodes, ... }: let
+    serverpostgres = { pkgs, nodes, config, ... }: let
       mailserverIP = nodes.mailserver.config.networking.primaryIPAddress;
     in
     {
@@ -77,6 +77,11 @@ in {
             name = "psycopg2";
             args.password = "synapse";
           };
+          redis = {
+            enabled = true;
+            host = "localhost";
+            port = config.services.redis.servers.matrix-synapse.port;
+          };
           tls_certificate_path = "${cert}";
           tls_private_key_path = "${key}";
           registration_shared_secret = registrationSharedSecret;
@@ -107,6 +112,11 @@ in {
         '';
       };
 
+      services.redis.servers.matrix-synapse = {
+        enable = true;
+        port = 6380;
+      };
+
       networking.extraHosts = ''
         ${mailserverIP} ${mailerDomain}
       '';
@@ -208,6 +218,9 @@ in {
     serverpostgres.wait_until_succeeds(
         "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
     )
+    serverpostgres.wait_until_succeeds(
+        "journalctl -u matrix-synapse.service | grep -q 'Connected to redis'"
+    )
     serverpostgres.require_unit_state("postgresql.service")
     serverpostgres.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} https://localhost:8448/")
     serverpostgres.succeed("obtain-token-and-register-email")
diff --git a/nixos/tests/mediamtx.nix b/nixos/tests/mediamtx.nix
new file mode 100644
index 0000000000000..8cacd02631d95
--- /dev/null
+++ b/nixos/tests/mediamtx.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "mediamtx";
+  meta.maintainers = with lib.maintainers; [ fpletz ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.mediamtx = {
+        enable = true;
+        settings = {
+          metrics = true;
+          paths.all.source = "publisher";
+        };
+      };
+
+      systemd.services.rtmp-publish = {
+        description = "Publish an RTMP stream to mediamtx";
+        after = [ "mediamtx.service" ];
+        bindsTo = [ "mediamtx.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "on-failure";
+          RestartSec = "1s";
+          TimeoutStartSec = "10s";
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+        };
+      };
+
+      systemd.services.rtmp-receive = {
+        description = "Receive an RTMP stream from mediamtx";
+        after = [ "rtmp-publish.service" ];
+        bindsTo = [ "rtmp-publish.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "on-failure";
+          RestartSec = "1s";
+          TimeoutStartSec = "10s";
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("mediamtx.service")
+    machine.wait_for_unit("rtmp-publish.service")
+    machine.wait_for_unit("rtmp-receive.service")
+    machine.wait_for_open_port(9998)
+    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
+    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
+  '';
+})
diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
index bd00e6143639c..b8f747b2a19f1 100644
--- a/nixos/tests/nixops/default.nix
+++ b/nixos/tests/nixops/default.nix
@@ -14,11 +14,12 @@ let
     # inherit testsForPackage;
   };
 
-  testsForPackage = lib.makeOverridable (args: lib.recurseIntoAttrs {
+  testsForPackage = args: lib.recurseIntoAttrs {
     legacyNetwork = testLegacyNetwork args;
-  });
+    passthru.override = args': testsForPackage (args // args');
+  };
 
-  testLegacyNetwork = { nixopsPkg }: pkgs.nixosTest ({
+  testLegacyNetwork = { nixopsPkg, ... }: pkgs.nixosTest ({
     name = "nixops-legacy-network";
     nodes = {
       deployer = { config, lib, nodes, pkgs, ... }: {
@@ -52,7 +53,7 @@ let
           chmod 0400 ~/.ssh/id_ed25519
         '';
         serverNetworkJSON = pkgs.writeText "server-network.json"
-          (builtins.toJSON nodes.server.config.system.build.networkConfig);
+          (builtins.toJSON nodes.server.system.build.networkConfig);
       in
       ''
         import shlex
diff --git a/nixos/tests/opensnitch.nix b/nixos/tests/opensnitch.nix
new file mode 100644
index 0000000000000..d84e4e0a935b5
--- /dev/null
+++ b/nixos/tests/opensnitch.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "opensnitch";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ onny ];
+  };
+
+  nodes = {
+    server =
+      { ... }: {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        services.caddy = {
+          enable = true;
+          virtualHosts."localhost".extraConfig = ''
+            respond "Hello, world!"
+          '';
+        };
+      };
+
+    clientBlocked =
+      { ... }: {
+        services.opensnitch = {
+          enable = true;
+          settings.DefaultAction = "deny";
+        };
+      };
+
+    clientAllowed =
+      { ... }: {
+        services.opensnitch = {
+          enable = true;
+          settings.DefaultAction = "deny";
+          rules = {
+            opensnitch = {
+              name = "curl";
+              enabled = true;
+              action = "allow";
+              duration = "always";
+              operator = {
+                type ="simple";
+                sensitive = false;
+                operand = "process.path";
+                data = "${pkgs.curl}/bin/curl";
+              };
+            };
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("caddy.service")
+    server.wait_for_open_port(80)
+
+    clientBlocked.wait_for_unit("opensnitchd.service")
+    clientBlocked.fail("curl http://server")
+
+    clientAllowed.wait_for_unit("opensnitchd.service")
+    clientAllowed.succeed("curl http://server")
+  '';
+})
diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix
index 22e720824c805..dae1306bd69d0 100644
--- a/nixos/tests/os-prober.nix
+++ b/nixos/tests/os-prober.nix
@@ -76,6 +76,7 @@ in {
       # nixos-rebuild needs must be included in the VM.
       system.extraDependencies = with pkgs;
         [
+          bintools
           brotli
           brotli.dev
           brotli.lib
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index 653fcc6edad11..dee6964644c5d 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -60,6 +60,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     with subtest("Open elementary terminal"):
         machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal >&2 &'")
         machine.wait_for_window("io.elementary.terminal")
+
+    with subtest("Check if gala has ever coredumped"):
+        machine.fail("coredumpctl --json=short | grep gala")
         machine.sleep(20)
         machine.screenshot("screen")
   '';
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 64e2811beb060..d86f8ac634e82 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -1200,7 +1200,7 @@ let
       };
       exporterTest = ''
         wait_until_succeeds(
-            'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Device unavailable"'
+            'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"'
         )
       '';
     };
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index f44dede7fef45..53595ae7d3e29 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -1,6 +1,6 @@
 # Test configuration switching.
 
-import ./make-test-python.nix ({ pkgs, ...} : let
+import ./make-test-python.nix ({ lib, pkgs, ...} : let
 
   # Simple service that can either be socket-activated or that will
   # listen on port 1234 if not socket-activated.
@@ -279,6 +279,28 @@ in {
           systemd.services.test-service.unitConfig.RefuseManualStart = true;
         };
 
+        unitWithTemplate.configuration = {
+          systemd.services."instantiated@".serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+            ExecStart = "${pkgs.coreutils}/bin/true";
+            ExecReload = "${pkgs.coreutils}/bin/true";
+          };
+          systemd.services."instantiated@one" = {
+            wantedBy = [ "multi-user.target" ];
+            overrideStrategy = "asDropin";
+          };
+          systemd.services."instantiated@two" = {
+            wantedBy = [ "multi-user.target" ];
+            overrideStrategy = "asDropin";
+          };
+        };
+
+        unitWithTemplateModified.configuration = {
+          imports = [ unitWithTemplate.configuration ];
+          systemd.services."instantiated@".serviceConfig.X-Test = "test";
+        };
+
         restart-and-reload-by-activation-script.configuration = {
           systemd.services = rec {
             simple-service = {
@@ -290,29 +312,50 @@ in {
                 ExecReload = "${pkgs.coreutils}/bin/true";
               };
             };
+            "templated-simple-service@" = simple-service;
+            "templated-simple-service@instance".overrideStrategy = "asDropin";
 
             simple-restart-service = simple-service // {
               stopIfChanged = false;
             };
+            "templated-simple-restart-service@" = simple-restart-service;
+            "templated-simple-restart-service@instance".overrideStrategy = "asDropin";
 
             simple-reload-service = simple-service // {
               reloadIfChanged = true;
             };
+            "templated-simple-reload-service@" = simple-reload-service;
+            "templated-simple-reload-service@instance".overrideStrategy = "asDropin";
 
             no-restart-service = simple-service // {
               restartIfChanged = false;
             };
+            "templated-no-restart-service@" = no-restart-service;
+            "templated-no-restart-service@instance".overrideStrategy = "asDropin";
 
             reload-triggers = simple-service // {
               wantedBy = [ "multi-user.target" ];
             };
+            "templated-reload-triggers@" = simple-service;
+            "templated-reload-triggers@instance" = {
+              overrideStrategy = "asDropin";
+              wantedBy = [ "multi-user.target" ];
+            };
 
             reload-triggers-and-restart-by-as = simple-service;
+            "templated-reload-triggers-and-restart-by-as@" = reload-triggers-and-restart-by-as;
+            "templated-reload-triggers-and-restart-by-as@instance".overrideStrategy = "asDropin";
 
             reload-triggers-and-restart = simple-service // {
               stopIfChanged = false; # easier to check for this
               wantedBy = [ "multi-user.target" ];
             };
+            "templated-reload-triggers-and-restart@" = simple-service;
+            "templated-reload-triggers-and-restart@instance" = {
+              overrideStrategy = "asDropin";
+              stopIfChanged = false; # easier to check for this
+              wantedBy = [ "multi-user.target" ];
+            };
           };
 
           system.activationScripts.restart-and-reload-test = {
@@ -332,12 +375,20 @@ in {
               simple-reload-service.service
               no-restart-service.service
               reload-triggers-and-restart-by-as.service
+              templated-simple-service@instance.service
+              templated-simple-restart-service@instance.service
+              templated-simple-reload-service@instance.service
+              templated-no-restart-service@instance.service
+              templated-reload-triggers-and-restart-by-as@instance.service
               EOF
 
               cat <<EOF >> "$g"
               reload-triggers.service
               reload-triggers-and-restart-by-as.service
               reload-triggers-and-restart.service
+              templated-reload-triggers@instance.service
+              templated-reload-triggers-and-restart-by-as@instance.service
+              templated-reload-triggers-and-restart@instance.service
               EOF
             '';
           };
@@ -346,6 +397,10 @@ in {
         restart-and-reload-by-activation-script-modified.configuration = {
           imports = [ restart-and-reload-by-activation-script.configuration ];
           systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test";
+          systemd.services."templated-reload-triggers-and-restart@instance" = {
+            overrideStrategy = "asDropin";
+            serviceConfig.X-Modified = "test";
+          };
         };
 
         simple-socket.configuration = {
@@ -507,6 +562,10 @@ in {
       set -o pipefail
       exec env -i "$@" | tee /dev/stderr
     '';
+
+    # 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);
   in /* python */ ''
     def switch_to_specialisation(system, name, action="test", fail=False):
         if name == "":
@@ -733,6 +792,16 @@ in {
         assert_contains(out, "\nstarting the following units: required-service.service\n")
         assert_lacks(out, "the following new units were started:")
 
+        # Ensure templated units are restarted when the base unit changes
+        switch_to_specialisation("${machine}", "unitWithTemplate")
+        out = switch_to_specialisation("${machine}", "unitWithTemplateModified")
+        assert_contains(out, "stopping the following units: instantiated@one.service, instantiated@two.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: instantiated@one.service, instantiated@two.service\n")
+        assert_lacks(out, "the following new units were started:")
+
     with subtest("failing units"):
         # Let the simple service fail
         switch_to_specialisation("${machine}", "simpleServiceModified")
@@ -896,15 +965,62 @@ in {
         assert_lacks(out, "NOT restarting the following changed units:")
         assert_lacks(out, "reloading the following units:")
         assert_lacks(out, "restarting the following units:")
-        assert_contains(out, "\nstarting the following units: no-restart-service.service, reload-triggers-and-restart-by-as.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
-        assert_contains(out, "the following new units were started: no-restart-service.service, reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
+        assert_contains(out, "\nstarting the following units: ${sortedUnits [
+          "no-restart-service.service"
+          "reload-triggers-and-restart-by-as.service"
+          "simple-reload-service.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-no-restart-service@instance.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-simple-reload-service@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        assert_contains(out, "the following new units were started: ${sortedUnits [
+          "no-restart-service.service"
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "system-templated\\\\x2dno\\\\x2drestart\\\\x2dservice.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart\\\\x2dby\\\\x2das.slice"
+          "system-templated\\\\x2dsimple\\\\x2dreload\\\\x2dservice.slice"
+          "system-templated\\\\x2dsimple\\\\x2drestart\\\\x2dservice.slice"
+          "system-templated\\\\x2dsimple\\\\x2dservice.slice"
+          "templated-no-restart-service@instance.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
         # Switch to the same system where the example services get restarted
         # and reloaded by the activation script
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service\n")
-        assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, simple-restart-service.service, simple-service.service\n")
+        assert_contains(out, "reloading the following units: ${sortedUnits [
+          "reload-triggers-and-restart.service"
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "restarting the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
         # Switch to the same system and see if the service gets restarted when it's modified
@@ -912,16 +1028,44 @@ in {
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: reload-triggers.service, simple-reload-service.service\n")
-        assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
+        assert_contains(out, "reloading the following units: ${sortedUnits [
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "restarting the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
         # The same, but in dry mode
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate")
         assert_lacks(out, "would stop the following units:")
         assert_lacks(out, "would NOT stop the following changed units:")
-        assert_contains(out, "would reload the following units: reload-triggers.service, simple-reload-service.service\n")
-        assert_contains(out, "would restart the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
+        assert_contains(out, "would reload the following units: ${sortedUnits [
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "would restart the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
         assert_lacks(out, "\nwould start the following units:")
 
     with subtest("socket-activated services"):
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index 84a4da5aa6ec5..c1f8637989e39 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -118,14 +118,11 @@ in
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.memtest86.enable = true;
-      nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
-        "memtest86-efi"
-      ];
     };
 
     testScript = ''
       machine.succeed("test -e /boot/loader/entries/memtest86.conf")
-      machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi")
+      machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
     '';
   };
 
@@ -152,15 +149,12 @@ in
       imports = [ common ];
       boot.loader.systemd-boot.memtest86.enable = true;
       boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
-      nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
-        "memtest86-efi"
-      ];
     };
 
     testScript = ''
       machine.fail("test -e /boot/loader/entries/memtest86.conf")
       machine.succeed("test -e /boot/loader/entries/apple.conf")
-      machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi")
+      machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
     '';
   };
 
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 062b125eb611e..e522d0679e151 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -519,4 +519,4 @@ in mapAttrs (mkVBoxTest false vboxVMs) {
     destroy_vm_test1()
     destroy_vm_test2()
   '';
-} // (lib.optionalAttrs enableUnfree unfreeTests)
+} // (optionalAttrs enableUnfree unfreeTests)
diff --git a/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
index 2fd3983e13ec3..715477191bfbc 100644
--- a/nixos/tests/web-apps/mastodon/remote-postgresql.nix
+++ b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
@@ -13,7 +13,7 @@ let
 in
 {
   name = "mastodon-remote-postgresql";
-  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin turion ];
+  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
 
   nodes = {
     database = {
diff --git a/nixos/tests/web-apps/netbox-upgrade.nix b/nixos/tests/web-apps/netbox-upgrade.nix
new file mode 100644
index 0000000000000..602cf8d889d4f
--- /dev/null
+++ b/nixos/tests/web-apps/netbox-upgrade.nix
@@ -0,0 +1,85 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: let
+  oldNetbox = pkgs.netbox_3_3;
+in {
+  name = "netbox-upgrade";
+
+  meta = with lib.maintainers; {
+    maintainers = [ minijackson ];
+  };
+
+  nodes.machine = { config, ... }: {
+    services.netbox = {
+      enable = true;
+      package = oldNetbox;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+
+      recommendedProxySettings = true;
+
+      virtualHosts.netbox = {
+        default = true;
+        locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+        locations."/static/".alias = "/var/lib/netbox/static/";
+      };
+    };
+
+    users.users.nginx.extraGroups = [ "netbox" ];
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+
+    specialisation.upgrade.configuration.services.netbox.package = lib.mkForce pkgs.netbox;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      apiVersion = version: lib.pipe version [
+        (lib.splitString ".")
+        (lib.take 2)
+        (lib.concatStringsSep ".")
+      ];
+      oldApiVersion = apiVersion oldNetbox.version;
+      newApiVersion = apiVersion pkgs.netbox.version;
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("netbox.target")
+      machine.wait_for_unit("nginx.service")
+      machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
+
+      def api_version(headers):
+          header = [header for header in headers.splitlines() if header.startswith("API-Version:")][0]
+          return header.split()[1]
+
+      def check_api_version(version):
+          headers = machine.succeed(
+            "curl -sSfL http://localhost/api/ --head -H 'Content-Type: application/json'"
+          )
+          assert api_version(headers) == version
+
+      with subtest("NetBox version is the old one"):
+          check_api_version("${oldApiVersion}")
+
+      # Somehow, even though netbox-housekeeping.service has After=netbox.service,
+      # netbox-housekeeping.service and netbox.service still get started at the
+      # same time, making netbox-housekeeping fail (can't really do some house
+      # keeping job if the database is not correctly formed).
+      #
+      # So we don't check that the upgrade went well, we just check that
+      # netbox.service is active, and that netbox-housekeeping can be run
+      # successfully afterwards.
+      #
+      # This is not good UX, but the system should be working nonetheless.
+      machine.execute("${nodes.machine.system.build.toplevel}/specialisation/upgrade/bin/switch-to-configuration test >&2")
+
+      machine.wait_for_unit("netbox.service")
+      machine.succeed("systemctl start netbox-housekeeping.service")
+
+      with subtest("NetBox version is the new one"):
+          check_api_version("${newApiVersion}")
+    '';
+})
diff --git a/nixos/tests/web-servers/agate.nix b/nixos/tests/web-servers/agate.nix
index e8d789a9ca44c..0de27b6f7d8df 100644
--- a/nixos/tests/web-servers/agate.nix
+++ b/nixos/tests/web-servers/agate.nix
@@ -20,7 +20,7 @@
     geminiserver.wait_for_open_port(1965)
 
     with subtest("check is serving over gemini"):
-      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+      response = geminiserver.succeed("${pkgs.gemget}/bin/gemget --header -o - gemini://localhost:1965")
       print(response)
       assert "Hello NixOS!" in response
   '';
diff --git a/nixos/tests/wrappers.nix b/nixos/tests/wrappers.nix
index 391e9b42b45bd..1f5f43286384c 100644
--- a/nixos/tests/wrappers.nix
+++ b/nixos/tests/wrappers.nix
@@ -84,6 +84,17 @@ in
       test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
       test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
 
+      # Test that in nonewprivs environment the wrappers simply exec their target.
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
+
       # We are only testing the permitted set, because it's easiest to look at with capsh.
       machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
       machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))