about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/adding-custom-packages.section.md35
-rw-r--r--nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml116
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2211.section.xml19
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md6
-rw-r--r--nixos/lib/make-multi-disk-zfs-image.nix2
-rw-r--r--nixos/lib/make-single-disk-zfs-image.nix2
-rw-r--r--nixos/modules/config/qt5.nix14
-rw-r--r--nixos/modules/config/xdg/portal.nix27
-rw-r--r--nixos/modules/hardware/ckb-next.nix2
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix2
-rw-r--r--nixos/modules/installer/netboot/netboot.nix2
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl6
-rw-r--r--nixos/modules/module-list.nix6
-rw-r--r--nixos/modules/programs/qt5ct.nix30
-rw-r--r--nixos/modules/programs/thunar.nix3
-rw-r--r--nixos/modules/programs/xfconf.nix27
-rw-r--r--nixos/modules/rename.nix2
-rw-r--r--nixos/modules/services/databases/redis.nix12
-rw-r--r--nixos/modules/services/development/jupyter/default.nix3
-rw-r--r--nixos/modules/services/development/jupyter/kernel-options.nix9
-rw-r--r--nixos/modules/services/mail/mailman.nix138
-rw-r--r--nixos/modules/services/matrix/synapse.xml235
-rw-r--r--nixos/modules/services/misc/dictd.nix4
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/networking/lokinet.nix157
-rw-r--r--nixos/modules/services/networking/radvd.nix17
-rw-r--r--nixos/modules/services/security/privacyidea.nix2
-rw-r--r--nixos/modules/services/security/vault.nix28
-rw-r--r--nixos/modules/services/system/cachix-agent/default.nix13
-rw-r--r--nixos/modules/services/web-apps/calibre-web.nix2
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixos/modules/services/web-apps/healthchecks.nix249
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix25
-rw-r--r--nixos/modules/services/web-apps/phylactery.nix51
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix5
-rw-r--r--nixos/modules/system/boot/systemd/user.nix1
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix4
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix84
-rw-r--r--nixos/modules/virtualisation/railcar.nix124
-rw-r--r--nixos/tests/all-tests.nix6
-rw-r--r--nixos/tests/cryptpad.nix18
-rw-r--r--nixos/tests/installed-tests/default.nix1
-rw-r--r--nixos/tests/installed-tests/geocode-glib.nix13
-rw-r--r--nixos/tests/jitsi-meet.nix3
-rw-r--r--nixos/tests/maddy.nix2
-rw-r--r--nixos/tests/nginx-http3.nix3
-rw-r--r--nixos/tests/non-default-filesystems.nix54
-rw-r--r--nixos/tests/prometheus-exporters.nix2
-rw-r--r--nixos/tests/swap-partition.nix48
-rw-r--r--nixos/tests/vault-dev.nix35
-rw-r--r--nixos/tests/web-apps/healthchecks.nix42
-rw-r--r--nixos/tests/web-apps/phylactery.nix20
-rw-r--r--nixos/tests/xrdp.nix2
54 files changed, 1329 insertions, 444 deletions
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixos/doc/manual/configuration/adding-custom-packages.section.md
index 5d1198fb0f418..9219396722f03 100644
--- a/nixos/doc/manual/configuration/adding-custom-packages.section.md
+++ b/nixos/doc/manual/configuration/adding-custom-packages.section.md
@@ -1,11 +1,18 @@
 # Adding Custom Packages {#sec-custom-packages}
 
 It's possible that a package you need is not available in NixOS. In that
-case, you can do two things. First, you can clone the Nixpkgs
-repository, add the package to your clone, and (optionally) submit a
-patch or pull request to have it accepted into the main Nixpkgs repository.
-This is described in detail in the [Nixpkgs manual](https://nixos.org/nixpkgs/manual).
-In short, you clone Nixpkgs:
+case, you can do two things. Either you can package it with Nix, or you can try
+to use prebuilt packages from upstream. Due to the peculiarities of NixOS, it
+is important to note that building software from source is often easier than
+using pre-built executables.
+
+## Building with Nix {#sec-custom-packages-nix}
+
+This can be done either in-tree or out-of-tree. For an in-tree build, you can
+clone the Nixpkgs repository, add the package to your clone, and (optionally)
+submit a patch or pull request to have it accepted into the main Nixpkgs
+repository. This is described in detail in the [Nixpkgs
+manual](https://nixos.org/nixpkgs/manual). In short, you clone Nixpkgs:
 
 ```ShellSession
 $ git clone https://github.com/NixOS/nixpkgs
@@ -72,3 +79,21 @@ $ nix-build my-hello.nix
 $ ./result/bin/hello
 Hello, world!
 ```
+
+## Using pre-built executables {#sec-custom-packages-prebuilt}
+
+Most pre-built executables will not work on NixOS. There are two notable
+exceptions: flatpaks and AppImages. For flatpaks see the [dedicated
+section](#module-services-flatpak). AppImages will not run "as-is" on NixOS.
+First you need to install `appimage-run`: add to `/etc/nixos/configuration.nix`
+
+```nix
+environment.systemPackages = [ pkgs.appimage-run ];
+```
+
+Then instead of running the AppImage "as-is", run `appimage-run foo.appimage`.
+
+To make other pre-built executables work on NixOS, you need to package them
+with Nix and special helpers like `autoPatchelfHook` or `buildFHSUserEnv`. See
+the [Nixpkgs manual](https://nixos.org/nixpkgs/manual) for details. This
+is complex and often doing a source build is easier.
diff --git a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
index 4fa40d61966e7..07f541666cbe1 100644
--- a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
+++ b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
@@ -2,40 +2,50 @@
   <title>Adding Custom Packages</title>
   <para>
     It’s possible that a package you need is not available in NixOS. In
-    that case, you can do two things. First, you can clone the Nixpkgs
-    repository, add the package to your clone, and (optionally) submit a
-    patch or pull request to have it accepted into the main Nixpkgs
-    repository. This is described in detail in the
-    <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs
-    manual</link>. In short, you clone Nixpkgs:
+    that case, you can do two things. Either you can package it with
+    Nix, or you can try to use prebuilt packages from upstream. Due to
+    the peculiarities of NixOS, it is important to note that building
+    software from source is often easier than using pre-built
+    executables.
   </para>
-  <programlisting>
+  <section xml:id="sec-custom-packages-nix">
+    <title>Building with Nix</title>
+    <para>
+      This can be done either in-tree or out-of-tree. For an in-tree
+      build, you can clone the Nixpkgs repository, add the package to
+      your clone, and (optionally) submit a patch or pull request to
+      have it accepted into the main Nixpkgs repository. This is
+      described in detail in the
+      <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs
+      manual</link>. In short, you clone Nixpkgs:
+    </para>
+    <programlisting>
 $ git clone https://github.com/NixOS/nixpkgs
 $ cd nixpkgs
 </programlisting>
-  <para>
-    Then you write and test the package as described in the Nixpkgs
-    manual. Finally, you add it to
-    <xref linkend="opt-environment.systemPackages" />, e.g.
-  </para>
-  <programlisting language="bash">
+    <para>
+      Then you write and test the package as described in the Nixpkgs
+      manual. Finally, you add it to
+      <xref linkend="opt-environment.systemPackages" />, e.g.
+    </para>
+    <programlisting language="bash">
 environment.systemPackages = [ pkgs.my-package ];
 </programlisting>
-  <para>
-    and you run <literal>nixos-rebuild</literal>, specifying your own
-    Nixpkgs tree:
-  </para>
-  <programlisting>
+    <para>
+      and you run <literal>nixos-rebuild</literal>, specifying your own
+      Nixpkgs tree:
+    </para>
+    <programlisting>
 # nixos-rebuild switch -I nixpkgs=/path/to/my/nixpkgs
 </programlisting>
-  <para>
-    The second possibility is to add the package outside of the Nixpkgs
-    tree. For instance, here is how you specify a build of the
-    <link xlink:href="https://www.gnu.org/software/hello/">GNU
-    Hello</link> package directly in
-    <literal>configuration.nix</literal>:
-  </para>
-  <programlisting language="bash">
+    <para>
+      The second possibility is to add the package outside of the
+      Nixpkgs tree. For instance, here is how you specify a build of the
+      <link xlink:href="https://www.gnu.org/software/hello/">GNU
+      Hello</link> package directly in
+      <literal>configuration.nix</literal>:
+    </para>
+    <programlisting language="bash">
 environment.systemPackages =
   let
     my-hello = with pkgs; stdenv.mkDerivation rec {
@@ -48,17 +58,17 @@ environment.systemPackages =
   in
   [ my-hello ];
 </programlisting>
-  <para>
-    Of course, you can also move the definition of
-    <literal>my-hello</literal> into a separate Nix expression, e.g.
-  </para>
-  <programlisting language="bash">
+    <para>
+      Of course, you can also move the definition of
+      <literal>my-hello</literal> into a separate Nix expression, e.g.
+    </para>
+    <programlisting language="bash">
 environment.systemPackages = [ (import ./my-hello.nix) ];
 </programlisting>
-  <para>
-    where <literal>my-hello.nix</literal> contains:
-  </para>
-  <programlisting language="bash">
+    <para>
+      where <literal>my-hello.nix</literal> contains:
+    </para>
+    <programlisting language="bash">
 with import &lt;nixpkgs&gt; {}; # bring all of Nixpkgs into scope
 
 stdenv.mkDerivation rec {
@@ -69,12 +79,40 @@ stdenv.mkDerivation rec {
   };
 }
 </programlisting>
-  <para>
-    This allows testing the package easily:
-  </para>
-  <programlisting>
+    <para>
+      This allows testing the package easily:
+    </para>
+    <programlisting>
 $ nix-build my-hello.nix
 $ ./result/bin/hello
 Hello, world!
 </programlisting>
+  </section>
+  <section xml:id="sec-custom-packages-prebuilt">
+    <title>Using pre-built executables</title>
+    <para>
+      Most pre-built executables will not work on NixOS. There are two
+      notable exceptions: flatpaks and AppImages. For flatpaks see the
+      <link linkend="module-services-flatpak">dedicated section</link>.
+      AppImages will not run <quote>as-is</quote> on NixOS. First you
+      need to install <literal>appimage-run</literal>: add to
+      <literal>/etc/nixos/configuration.nix</literal>
+    </para>
+    <programlisting language="bash">
+environment.systemPackages = [ pkgs.appimage-run ];
+</programlisting>
+    <para>
+      Then instead of running the AppImage <quote>as-is</quote>, run
+      <literal>appimage-run foo.appimage</literal>.
+    </para>
+    <para>
+      To make other pre-built executables work on NixOS, you need to
+      package them with Nix and special helpers like
+      <literal>autoPatchelfHook</literal> or
+      <literal>buildFHSUserEnv</literal>. See the
+      <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs
+      manual</link> for details. This is complex and often doing a
+      source build is easier.
+    </para>
+  </section>
 </section>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 91d169d42ced3..2aa88d4bd93d4 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -287,6 +287,13 @@
       </listitem>
       <listitem>
         <para>
+          The Redis module now disables RDB persistence when
+          <literal>services.redis.servers.&lt;name&gt;.save = []</literal>
+          instead of using the Redis default.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           Matrix Synapse now requires entries in the
           <literal>state_group_edges</literal> table to be unique, in
           order to prevent accidentally introducing duplicate
@@ -314,6 +321,11 @@
       </listitem>
       <listitem>
         <para>
+          Add udev rules for the Teensy family of microcontrollers.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           There is a new module for the <literal>thunar</literal>
           program (the Xfce file manager), which depends on the
           <literal>xfconf</literal> dbus service, and also has a dbus
@@ -324,6 +336,13 @@
           release it may be removed.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          There is a new module for the <literal>xfconf</literal>
+          program (the Xfce configuration storage system), which has a
+          dbus service.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 3f799a2ad68f0..15c95c9206b22 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -110,6 +110,8 @@ Use `configure.packages` instead.
 
 - A new module was added for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`.
 
+- The Redis module now disables RDB persistence when `services.redis.servers.<name>.save = []` instead of using the Redis default.
+
 - Matrix Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation.
 
 - `dockerTools.buildImage` deprecates the misunderstood `contents` parameter, in favor of `copyToRoot`.
@@ -117,6 +119,10 @@ Use `configure.packages` instead.
 
 - memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2. It is now the upstream version from https://www.memtest.org/, as coreboot's fork is no longer available.
 
+- Add udev rules for the Teensy family of microcontrollers.
+
 - There is a new module for the `thunar` program (the Xfce file manager), which depends on the `xfconf` dbus service, and also has a dbus service and a systemd unit. The option `services.xserver.desktopManager.xfce.thunarPlugins` has been renamed to `programs.thunar.plugins`, and in a future release it may be removed.
 
+- There is a new module for the `xfconf` program (the Xfce configuration storage system), which has a dbus service.
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/lib/make-multi-disk-zfs-image.nix b/nixos/lib/make-multi-disk-zfs-image.nix
index a84732aa11712..0a894c8b9888f 100644
--- a/nixos/lib/make-multi-disk-zfs-image.nix
+++ b/nixos/lib/make-multi-disk-zfs-image.nix
@@ -128,7 +128,7 @@ let
       gptfdisk
       nix
       parted
-      utillinux
+      util-linux
       zfs
     ]
   );
diff --git a/nixos/lib/make-single-disk-zfs-image.nix b/nixos/lib/make-single-disk-zfs-image.nix
index 9310febd91792..98150584fb3e4 100644
--- a/nixos/lib/make-single-disk-zfs-image.nix
+++ b/nixos/lib/make-single-disk-zfs-image.nix
@@ -116,7 +116,7 @@ let
       gptfdisk
       nix
       parted
-      utillinux
+      util-linux
       zfs
     ]
   );
diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix
index eabba9ad95f00..542a96ba6b068 100644
--- a/nixos/modules/config/qt5.nix
+++ b/nixos/modules/config/qt5.nix
@@ -8,14 +8,17 @@ let
 
   isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"];
   isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]);
+  isQt5ct = cfg.platformTheme == "qt5ct";
 
   packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ]
     else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ]
+    else if isQt5ct then [ pkgs.libsForQt5.qt5ct ]
     else throw "`qt5.platformTheme` ${cfg.platformTheme} and `qt5.style` ${cfg.style} are not compatible.";
 
 in
 
 {
+  meta.maintainers = [ maintainers.romildo ];
 
   options = {
     qt5 = {
@@ -26,11 +29,13 @@ in
         type = types.enum [
           "gtk2"
           "gnome"
+          "qt5ct"
         ];
         example = "gnome";
         relatedPackages = [
           "qgnomeplatform"
           ["libsForQt5" "qtstyleplugins"]
+          ["libsForQt5" "qt5ct"]
         ];
         description = ''
           Selects the platform theme to use for Qt5 applications.</para>
@@ -48,6 +53,13 @@ in
                 <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
               </para></listitem>
             </varlistentry>
+            <varlistentry>
+              <term><literal>qt5ct</literal></term>
+              <listitem><para>Use Qt style set using the
+                <link xlink:href="https://sourceforge.net/projects/qt5ct/">qt5ct</link>
+                application.
+              </para></listitem>
+            </varlistentry>
           </variablelist>
         '';
       };
@@ -96,7 +108,7 @@ in
 
     environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme;
 
-    environment.variables.QT_STYLE_OVERRIDE = cfg.style;
+    environment.variables.QT_STYLE_OVERRIDE = mkIf (! isQt5ct) cfg.style;
 
     environment.systemPackages = packages;
 
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index 088f2af59e22a..1e6ddd7c4a26b 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -1,10 +1,30 @@
 { config, pkgs, lib, ... }:
 
-with lib;
+let
+  inherit (lib)
+    mkEnableOption
+    mkIf
+    mkOption
+    mkRenamedOptionModule
+    teams
+    types;
+in
 
 {
   imports = [
     (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
+
+    ({ config, lib, options, ... }:
+      let
+        from = [ "xdg" "portal" "gtkUsePortal" ];
+        fromOpt = lib.getAttrFromPath from options;
+      in
+      {
+        warnings = lib.mkIf config.xdg.portal.gtkUsePortal [
+          "The option `${lib.showOption from}' defined in ${lib.showFiles fromOpt.files} has been deprecated. Setting the variable globally with `environment.sessionVariables' NixOS option can have unforseen side-effects."
+        ];
+      }
+    )
   ];
 
   meta = {
@@ -32,11 +52,12 @@ with lib;
 
     gtkUsePortal = mkOption {
       type = types.bool;
+      visible = false;
       default = false;
       description = ''
         Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>.
-        This is needed for packages ran outside Flatpak to respect and use XDG Desktop Portals.
-        For example, you'd need to set this for non-flatpak Firefox to use native filechoosers.
+        This will force GTK-based programs ran outside Flatpak to respect and use XDG Desktop Portals
+        for features like file chooser but it is an unsupported hack that can easily break things.
         Defaults to <literal>false</literal> to respect its opt-in nature.
       '';
     };
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index b2bbd77c9d7fa..751ed663aab57 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -48,6 +48,6 @@ in
     };
 
     meta = {
-      maintainers = with lib.maintainers; [ kierdavis ];
+      maintainers = with lib.maintainers; [ superherointj ];
     };
   }
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index fa6f0b8fc84a3..120348a74bfbe 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -40,7 +40,7 @@ let
       homepage = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/";
       license = licenses.unfreeRedistributable;
       platforms = [ "x86_64-linux" ];
-      maintainers = with maintainers; [ volth ];
+      maintainers = with maintainers; [ ];
     };
   };
 in {
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 3127bdc436f92..fed6a7c372879 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -81,7 +81,7 @@ with lib;
 
 
     # Create the initrd
-    system.build.netbootRamdisk = pkgs.makeInitrd {
+    system.build.netbootRamdisk = pkgs.makeInitrdNG {
       inherit (config.boot.initrd) compressor;
       prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
 
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 1935d8252607c..0e6320e4695c3 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -300,6 +300,12 @@ if ($virt eq "oracle") {
     push @attrs, "virtualisation.virtualbox.guest.enable = true;"
 }
 
+# Check if we're a Parallels guest. If so, enable the guest additions.
+# It is blocked by https://github.com/systemd/systemd/pull/23859
+if ($virt eq "parallels") {
+    push @attrs, "hardware.parallels.enable = true;";
+    push @attrs, "nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ \"prl-tools\" ];";
+}
 
 # Likewise for QEMU.
 if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 759c31ef28b01..d04a4f93555fc 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -230,6 +230,7 @@
   ./programs/weylus.nix
   ./programs/wireshark.nix
   ./programs/wshowkeys.nix
+  ./programs/xfconf.nix
   ./programs/xfs_quota.nix
   ./programs/xonsh.nix
   ./programs/xss-lock.nix
@@ -826,6 +827,7 @@
   ./services/networking/libreswan.nix
   ./services/networking/lldpd.nix
   ./services/networking/logmein-hamachi.nix
+  ./services/networking/lokinet.nix
   ./services/networking/lxd-image-server.nix
   ./services/networking/magic-wormhole-mailbox-server.nix
   ./services/networking/matterbridge.nix
@@ -1045,7 +1047,6 @@
   ./services/web-apps/code-server.nix
   ./services/web-apps/baget.nix
   ./services/web-apps/convos.nix
-  ./services/web-apps/cryptpad.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
@@ -1057,6 +1058,7 @@
   ./services/web-apps/gerrit.nix
   ./services/web-apps/gotify-server.nix
   ./services/web-apps/grocy.nix
+  ./services/web-apps/healthchecks.nix
   ./services/web-apps/hedgedoc.nix
   ./services/web-apps/hledger-web.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
@@ -1080,6 +1082,7 @@
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
+  ./services/web-apps/phylactery.nix
   ./services/web-apps/pict-rs.nix
   ./services/web-apps/peertube.nix
   ./services/web-apps/plantuml-server.nix
@@ -1271,7 +1274,6 @@
   ./virtualisation/parallels-guest.nix
   ./virtualisation/podman/default.nix
   ./virtualisation/qemu-guest-agent.nix
-  ./virtualisation/railcar.nix
   ./virtualisation/spice-usb-redirection.nix
   ./virtualisation/virtualbox-guest.nix
   ./virtualisation/virtualbox-host.nix
diff --git a/nixos/modules/programs/qt5ct.nix b/nixos/modules/programs/qt5ct.nix
index 88e861bf4031a..3ff47b355915b 100644
--- a/nixos/modules/programs/qt5ct.nix
+++ b/nixos/modules/programs/qt5ct.nix
@@ -1,31 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ lib, ... }:
 
 with lib;
 
 {
-  meta.maintainers = [ maintainers.romildo ];
-
-  ###### interface
-  options = {
-    programs.qt5ct = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Whether to enable the Qt5 Configuration Tool (qt5ct), a
-          program that allows users to configure Qt5 settings (theme,
-          font, icons, etc.) under desktop environments or window
-          manager without Qt integration.
-
-          Official home page: <link xlink:href="https://sourceforge.net/projects/qt5ct/">https://sourceforge.net/projects/qt5ct/</link>
-        '';
-      };
-    };
-  };
-
-  ###### implementation
-  config = mkIf config.programs.qt5ct.enable {
-    environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct";
-    environment.systemPackages = with pkgs; [ libsForQt5.qt5ct ];
-  };
+  imports = [
+    (mkRemovedOptionModule [ "programs" "qt5ct" "enable" ] "Use qt5.platformTheme = \"qt5ct\" instead.")
+  ];
 }
diff --git a/nixos/modules/programs/thunar.nix b/nixos/modules/programs/thunar.nix
index 343f84698672a..5ea2982dd93cf 100644
--- a/nixos/modules/programs/thunar.nix
+++ b/nixos/modules/programs/thunar.nix
@@ -33,12 +33,13 @@ in {
 
       services.dbus.packages = [
         package
-        pkgs.xfce.xfconf
       ];
 
       systemd.packages = [
         package
       ];
+
+      programs.xfconf.enable = true;
     }
   );
 }
diff --git a/nixos/modules/programs/xfconf.nix b/nixos/modules/programs/xfconf.nix
new file mode 100644
index 0000000000000..8e854b40e513d
--- /dev/null
+++ b/nixos/modules/programs/xfconf.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.xfconf;
+
+in {
+  meta = {
+    maintainers = teams.xfce.members;
+  };
+
+  options = {
+    programs.xfconf = {
+      enable = mkEnableOption "Xfconf, the Xfce configuration storage system";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.xfce.xfconf
+    ];
+
+    services.dbus.packages = [
+      pkgs.xfce.xfconf
+    ];
+  };
+}
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 7a6a6b5ed30bb..22fcb72e9ff40 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -68,6 +68,7 @@ with lib;
       prey-bash-client is deprecated upstream
     '')
     (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs")
+    (mkRemovedOptionModule [ "services" "railcar" ] "the corresponding package has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "seeks" ] "")
     (mkRemovedOptionModule [ "services" "ssmtp" ] ''
       The ssmtp package and the corresponding module have been removed due to
@@ -98,6 +99,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index a1bd73c9e371d..5532c54019706 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -166,7 +166,11 @@ in {
             save = mkOption {
               type = with types; listOf (listOf int);
               default = [ [900 1] [300 10] [60 10000] ];
-              description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
+              description = mdDoc ''
+                The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.
+
+                If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence).
+              '';
             };
 
             slaveOf = mkOption {
@@ -268,7 +272,11 @@ in {
               syslog-enabled = config.syslog;
               databases = config.databases;
               maxclients = config.maxclients;
-              save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
+              save = if config.save == []
+                then ''""'' # Disable saving with `save = ""`
+                else map
+                  (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}")
+                  config.save;
               dbfilename = "dump.rdb";
               dir = "/var/lib/${redisName name}";
               appendOnly = config.appendOnly;
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index 4eacc4782a9a4..7c86e8b647854 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -143,6 +143,9 @@ in {
             language = "python";
             logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
             logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
+            extraPaths = {
+              "cool.txt" = pkgs.writeText "cool" "cool content";
+            };
           };
         }
       '';
diff --git a/nixos/modules/services/development/jupyter/kernel-options.nix b/nixos/modules/services/development/jupyter/kernel-options.nix
index 348a8b44b382b..0a9eaafa31856 100644
--- a/nixos/modules/services/development/jupyter/kernel-options.nix
+++ b/nixos/modules/services/development/jupyter/kernel-options.nix
@@ -56,5 +56,14 @@ with lib;
         Path to 64x64 logo png.
       '';
     };
+
+    extraPaths = mkOption {
+      type = types.attrsOf types.path;
+      default = { };
+      example = literalExpression ''"{ examples = ''${env.sitePack}/IRkernel/kernelspec/kernel.js"; }'';
+      description = ''
+        Extra paths to link in kernel directory
+      '';
+    };
   };
 }
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index 5b714c384de3f..11ea169fe2691 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.mailman;
 
-  inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; })
+  inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; withLDAP = cfg.ldap.enable; })
     mailmanEnv webEnv;
 
   withPostgresql = config.services.postgresql.enable;
@@ -87,6 +87,114 @@ in {
         description = "Enable Mailman on this host. Requires an active MTA on the host (e.g. Postfix).";
       };
 
+      ldap = {
+        enable = mkEnableOption "LDAP auth";
+        serverUri = mkOption {
+          type = types.str;
+          example = "ldaps://ldap.host";
+          description = ''
+            LDAP host to connect against.
+          '';
+        };
+        bindDn = mkOption {
+          type = types.str;
+          example = "cn=root,dc=nixos,dc=org";
+          description = ''
+            Service account to bind against.
+          '';
+        };
+        bindPasswordFile = mkOption {
+          type = types.str;
+          example = "/run/secrets/ldap-bind";
+          description = ''
+            Path to the file containing the bind password of the servie account
+            defined by <xref linkend="opt-services.mailman.ldap.bindDn" />.
+          '';
+        };
+        superUserGroup = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "cn=admin,ou=groups,dc=nixos,dc=org";
+          description = ''
+            Group where a user must be a member of to gain superuser rights.
+          '';
+        };
+        userSearch = {
+          query = mkOption {
+            type = types.str;
+            example = "(&(objectClass=inetOrgPerson)(|(uid=%(user)s)(mail=%(user)s)))";
+            description = ''
+              Query to find a user in the LDAP database.
+            '';
+          };
+          ou = mkOption {
+            type = types.str;
+            example = "ou=users,dc=nixos,dc=org";
+            description = ''
+              Organizational unit to look up a user.
+            '';
+          };
+        };
+        groupSearch = {
+          type = mkOption {
+            type = types.enum [
+              "posixGroup" "groupOfNames" "memberDNGroup" "nestedMemberDNGroup" "nestedGroupOfNames"
+              "groupOfUniqueNames" "nestedGroupOfUniqueNames" "activeDirectoryGroup" "nestedActiveDirectoryGroup"
+              "organizationalRoleGroup" "nestedOrganizationalRoleGroup"
+            ];
+            default = "posixGroup";
+            apply = v: "${toUpper (substring 0 1 v)}${substring 1 (stringLength v) v}Type";
+            description = ''
+              Type of group to perform a group search against.
+            '';
+          };
+          query = mkOption {
+            type = types.str;
+            example = "(objectClass=groupOfNames)";
+            description = ''
+              Query to find a group associated to a user in the LDAP database.
+            '';
+          };
+          ou = mkOption {
+            type = types.str;
+            example = "ou=groups,dc=nixos,dc=org";
+            description = ''
+              Organizational unit to look up a group.
+            '';
+          };
+        };
+        attrMap = {
+          username = mkOption {
+            default = "uid";
+            type = types.str;
+            description = ''
+              LDAP-attribute that corresponds to the <literal>username</literal>-attribute in mailman.
+            '';
+          };
+          firstName = mkOption {
+            default = "givenName";
+            type = types.str;
+            description = ''
+              LDAP-attribute that corresponds to the <literal>firstName</literal>-attribute in mailman.
+            '';
+          };
+          lastName = mkOption {
+            default = "sn";
+            type = types.str;
+            description = ''
+              LDAP-attribute that corresponds to the <literal>lastName</literal>-attribute in mailman.
+            '';
+          };
+          email = mkOption {
+            default = "mail";
+            type = types.str;
+            description = ''
+              LDAP-attribute that corresponds to the <literal>email</literal>-attribute in mailman.
+            '';
+          };
+        };
+      };
+
       enablePostfix = mkOption {
         type = types.bool;
         default = true;
@@ -274,6 +382,34 @@ in {
 
       with open('/var/lib/mailman-web/settings_local.json') as f:
           globals().update(json.load(f))
+
+      ${optionalString (cfg.ldap.enable) ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, ${cfg.ldap.groupSearch.type}
+        AUTH_LDAP_SERVER_URI = "${cfg.ldap.serverUri}"
+        AUTH_LDAP_BIND_DN = "${cfg.ldap.bindDn}"
+        with open("${cfg.ldap.bindPasswordFile}") as f:
+            AUTH_LDAP_BIND_PASSWORD = f.read().rstrip('\n')
+        AUTH_LDAP_USER_SEARCH = LDAPSearch("${cfg.ldap.userSearch.ou}",
+            ldap.SCOPE_SUBTREE, "${cfg.ldap.userSearch.query}")
+        AUTH_LDAP_GROUP_TYPE = ${cfg.ldap.groupSearch.type}()
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch("${cfg.ldap.groupSearch.ou}",
+            ldap.SCOPE_SUBTREE, "${cfg.ldap.groupSearch.query}")
+        AUTH_LDAP_USER_ATTR_MAP = {
+          ${concatStrings (flip mapAttrsToList cfg.ldap.attrMap (key: value: ''
+            "${key}": "${value}",
+          ''))}
+        }
+        ${optionalString (cfg.ldap.superUserGroup != null) ''
+          AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+            "is_superuser": "${cfg.ldap.superUserGroup}"
+          }
+        ''}
+        AUTHENTICATION_BACKENDS = (
+            "django_auth_ldap.backend.LDAPBackend",
+            "django.contrib.auth.backends.ModelBackend"
+        )
+      ''}
     '';
 
     services.nginx = mkIf (cfg.serve.enable && cfg.webHosts != []) {
diff --git a/nixos/modules/services/matrix/synapse.xml b/nixos/modules/services/matrix/synapse.xml
index cf33957d58ece..65bc53d33ac34 100644
--- a/nixos/modules/services/matrix/synapse.xml
+++ b/nixos/modules/services/matrix/synapse.xml
@@ -33,21 +33,26 @@
    <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation">
    installation instructions of Synapse </link>.
 <programlisting>
-{ pkgs, lib, ... }:
+{ pkgs, lib, config, ... }:
 let
-  fqdn =
-    let
-      join = hostName: domain: hostName + lib.optionalString (domain != null) ".${domain}";
-    in join config.networking.hostName config.networking.domain;
-in {
-  networking = {
-    <link linkend="opt-networking.hostName">hostName</link> = "myhostname";
-    <link linkend="opt-networking.domain">domain</link> = "example.org";
+  fqdn = "${config.networking.hostName}.${config.networking.domain}";
+  clientConfig = {
+    "m.homeserver".base_url = "https://${fqdn}";
+    "m.identity_server" = {};
   };
-  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+  serverConfig."m.server" = "${config.services.matrix-synapse.settings.server_name}:443";
+  mkWellKnown = data: ''
+    add_header Content-Type application/json;
+    add_header Access-Control-Allow-Origin *;
+    return 200 '${builtins.toJSON data}';
+  '';
+in {
+  <xref linkend="opt-networking.hostName" /> = "myhostname";
+  <xref linkend="opt-networking.domain" /> = "example.org";
+  <xref linkend="opt-networking.firewall.allowedTCPPorts" /> = [ 80 443 ];
 
-  <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true;
-  <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" ''
+  <xref linkend="opt-services.postgresql.enable" /> = true;
+  <xref linkend="opt-services.postgresql.initialScript" /> = pkgs.writeText "synapse-init.sql" ''
     CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
     CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
       TEMPLATE template0
@@ -57,78 +62,41 @@ in {
 
   services.nginx = {
     <link linkend="opt-services.nginx.enable">enable</link> = true;
-    # only recommendedProxySettings and recommendedGzipSettings are strictly required,
-    # but the rest make sense as well
     <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
     <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
     <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
     <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
-
     <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-      # This host section can be placed on a different host than the rest,
-      # i.e. to delegate from the host being accessible as ${config.networking.domain}
-      # to another host actually running the Matrix homeserver.
-      "${config.networking.domain}" = {
+      "${config.networking.domain}" = { <co xml:id='ex-matrix-synapse-dns' />
         <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
         <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> =
-          let
-            # use 443 instead of the default 8448 port to unite
-            # the client-server and server-server port for simplicity
-            server = { "m.server" = "${fqdn}:443"; };
-          in ''
-            add_header Content-Type application/json;
-            return 200 '${builtins.toJSON server}';
-          '';
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> =
-          let
-            client = {
-              "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
-              "m.identity_server" =  { "base_url" = "https://vector.im"; };
-            };
-          # ACAO required to allow element-web on any URL to request this json file
-          in ''
-            add_header Content-Type application/json;
-            add_header Access-Control-Allow-Origin *;
-            return 200 '${builtins.toJSON client}';
-          '';
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> = mkWellKnown serverConfig; <co xml:id='ex-matrix-synapse-well-known-server' />
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> = mkWellKnown clientConfig; <co xml:id='ex-matrix-synapse-well-known-client' />
       };
-
-      # Reverse proxy for Matrix client-server and server-server communication
-      ${fqdn} = {
+      "${fqdn}" = {
         <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
         <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-
-        # Or do a redirect instead of the 404, or whatever is appropriate for you.
-        # But do not put a Matrix Web client here! See the Element web section below.
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = ''
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' <co xml:id='ex-matrix-synapse-rev-default' />
           return 404;
         '';
-
-        # forward all Matrix API calls to the synapse Matrix homeserver
-        locations."/_matrix" = {
-          <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing /
-        };
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_matrix".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-proxy-pass' />
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_synapse/client".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-client' />
       };
     };
   };
+
   services.matrix-synapse = {
     <link linkend="opt-services.matrix-synapse.enable">enable</link> = true;
-    <link linkend="opt-services.matrix-synapse.settings.server_name">server_name</link> = config.networking.domain;
-    <link linkend="opt-services.matrix-synapse.settings.listeners">listeners</link> = [
-      {
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
+    <link linkend="opt-services.matrix-synapse.settings.server_name">settings.server_name</link> = config.networking.domain;
+    <link linkend="opt-services.matrix-synapse.settings.listeners">settings.listeners</link> = [
+      { <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
         <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_addresses</link> = [ "::1" ];
         <link linkend="opt-services.matrix-synapse.settings.listeners._.type">type</link> = "http";
         <link linkend="opt-services.matrix-synapse.settings.listeners._.tls">tls</link> = false;
         <link linkend="opt-services.matrix-synapse.settings.listeners._.x_forwarded">x_forwarded</link> = true;
         <link linkend="opt-services.matrix-synapse.settings.listeners._.resources">resources</link> = [ {
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "client" ];
+          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "client" "federation" ];
           <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = true;
-        } {
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "federation" ];
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = false;
         } ];
       }
     ];
@@ -136,20 +104,59 @@ in {
 }
 </programlisting>
   </para>
-
-  <para>
-   If the <code>A</code> and <code>AAAA</code> DNS records on
-   <literal>example.org</literal> do not point on the same host as the records
-   for <code>myhostname.example.org</code>, you can easily move the
-   <code>/.well-known</code> virtualHost section of the code to the host that
-   is serving <literal>example.org</literal>, while the rest stays on
-   <literal>myhostname.example.org</literal> with no other changes required.
-   This pattern also allows to seamlessly move the homeserver from
-   <literal>myhostname.example.org</literal> to
-   <literal>myotherhost.example.org</literal> by only changing the
-   <code>/.well-known</code> redirection target.
-  </para>
-
+  <calloutlist>
+   <callout arearefs='ex-matrix-synapse-dns'>
+    <para>
+     If the <code>A</code> and <code>AAAA</code> DNS records on
+     <literal>example.org</literal> do not point on the same host as the records
+     for <code>myhostname.example.org</code>, you can easily move the
+     <code>/.well-known</code> virtualHost section of the code to the host that
+     is serving <literal>example.org</literal>, while the rest stays on
+     <literal>myhostname.example.org</literal> with no other changes required.
+     This pattern also allows to seamlessly move the homeserver from
+     <literal>myhostname.example.org</literal> to
+     <literal>myotherhost.example.org</literal> by only changing the
+     <code>/.well-known</code> redirection target.
+    </para>
+   </callout>
+   <callout arearefs='ex-matrix-synapse-well-known-server'>
+    <para>
+     This section is not needed if the <link linkend="opt-services.matrix-synapse.settings.server_name">server_name</link>
+     of <package>matrix-synapse</package> is equal to the domain (i.e.
+     <literal>example.org</literal> from <literal>@foo:example.org</literal>)
+     and the federation port is 8448.
+     Further reference can be found in the <link xlink:href="https://matrix-org.github.io/synapse/latest/delegate.html">docs
+     about delegation</link>.
+    </para>
+   </callout>
+   <callout arearefs='ex-matrix-synapse-well-known-client'>
+    <para>
+     This is usually needed for homeserver discovery (from e.g. other Matrix clients).
+     Further reference can be found in the <link xlink:href="https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient">upstream docs</link>
+    </para>
+   </callout>
+   <callout arearefs='ex-matrix-synapse-rev-default'>
+    <para>
+     It's also possible to do a redirect here or something else, this vhost is not
+     needed for Matrix. It's recommended though to <emphasis>not put</emphasis> element
+     here, see also the <link linkend='ex-matrix-synapse-rev-default'>section about Element</link>.
+    </para>
+   </callout>
+   <callout arearefs='ex-matrix-synapse-rev-proxy-pass'>
+    <para>
+     Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
+     <emphasis>must not</emphasis> be used here.
+    </para>
+   </callout>
+   <callout arearefs='ex-matrix-synapse-rev-client'>
+    <para>
+     Forward requests for e.g. SSO and password-resets.
+    </para>
+   </callout>
+  </calloutlist>
+ </section>
+ <section xml:id="module-services-matrix-register-users">
+  <title>Registering Matrix users</title>
   <para>
    If you want to run a server with public registration by anybody, you can
    then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.settings.enable_registration</link> =
@@ -159,7 +166,7 @@ in {
    To create a new user or admin, run the following after you have set the secret
    and have rebuilt NixOS:
 <screen>
-<prompt>$ </prompt>nix run nixpkgs.matrix-synapse
+<prompt>$ </prompt>nix-shell -p matrix-synapse
 <prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008
 <prompt>New user localpart: </prompt><replaceable>your-username</replaceable>
 <prompt>Password:</prompt>
@@ -168,12 +175,51 @@ in {
 Success!
 </screen>
    In the example, this would create a user with the Matrix Identifier
-   <literal>@your-username:example.org</literal>. Note that the registration
-   secret ends up in the nix store and therefore is world-readable by any user
-   on your machine, so it makes sense to only temporarily activate the
-   <link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">registration_shared_secret</link>
-   option until a better solution for NixOS is in place.
+   <literal>@your-username:example.org</literal>.
+   <warning>
+    <para>
+     When using <xref linkend="opt-services.matrix-synapse.settings.registration_shared_secret" />, the secret
+     will end up in the world-readable store. Instead it's recommended to deploy the secret
+     in an additional file like this:
+     <itemizedlist>
+      <listitem>
+       <para>
+        Create a file with the following contents:
+<programlisting>registration_shared_secret: your-very-secret-secret</programlisting>
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        Deploy the file with a secret-manager such as <link xlink:href="https://nixops.readthedocs.io/en/latest/overview.html#managing-keys"><option>deployment.keys</option></link>
+        from <citerefentry><refentrytitle>nixops</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        or <link xlink:href="https://github.com/Mic92/sops-nix/">sops-nix</link> to
+        e.g. <filename>/run/secrets/matrix-shared-secret</filename> and ensure that it's readable
+        by <package>matrix-synapse</package>.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        Include the file like this in your configuration:
+<programlisting>
+{
+  <xref linkend="opt-services.matrix-synapse.extraConfigFiles" /> = [
+    "/run/secrets/matrix-shared-secret"
+  ];
+}
+</programlisting>
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </warning>
   </para>
+  <note>
+   <para>
+    It's also possible to user alternative authentication mechanism such as
+    <link xlink:href="https://github.com/matrix-org/matrix-synapse-ldap3">LDAP (via <literal>matrix-synapse-ldap3</literal>)</link>
+    or <link xlink:href="https://matrix-org.github.io/synapse/latest/openid.html">OpenID</link>.
+   </para>
+  </note>
  </section>
  <section xml:id="module-services-matrix-element-web">
   <title>Element (formerly known as Riot) Web Client</title>
@@ -206,10 +252,7 @@ Success!
 
     <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override {
       conf = {
-        default_server_config."m.homeserver" = {
-          "base_url" = "https://${fqdn}";
-          "server_name" = "${fqdn}";
-        };
+        default_server_config = clientConfig; # see `clientConfig` from the snippet above.
       };
     };
   };
@@ -217,15 +260,17 @@ Success!
 </programlisting>
   </para>
 
-  <para>
-   Note that the Element developers do not recommend running Element and your Matrix
-   homeserver on the same fully-qualified domain name for security reasons. In
-   the example, this means that you should not reuse the
-   <literal>myhostname.example.org</literal> virtualHost to also serve Element,
-   but instead serve it on a different subdomain, like
-   <literal>element.example.org</literal> in the example. See the
-   <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Element
-   Important Security Notes</link> for more information on this subject.
-  </para>
+  <note>
+   <para>
+    The Element developers do not recommend running Element and your Matrix
+    homeserver on the same fully-qualified domain name for security reasons. In
+    the example, this means that you should not reuse the
+    <literal>myhostname.example.org</literal> virtualHost to also serve Element,
+    but instead serve it on a different subdomain, like
+    <literal>element.example.org</literal> in the example. See the
+    <link xlink:href="https://github.com/vector-im/element-web/tree/v1.10.0#important-security-notes">Element
+    Important Security Notes</link> for more information on this subject.
+   </para>
+  </note>
  </section>
 </chapter>
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 96e2a4e7c2602..8cb51bb0b7a7f 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -45,6 +45,10 @@ in
     # get the command line client on system path to make some use of the service
     environment.systemPackages = [ pkgs.dict ];
 
+    environment.etc."dict.conf".text = ''
+      server localhost
+    '';
+
     users.users.dictd =
       { group = "dictd";
         description = "DICT.org dictd server";
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 0b8bd08a22bc5..ee59cea38dfd1 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -1063,7 +1063,7 @@ in {
         chown ${cfg.user}:${cfg.group} ${cfg.registry.certFile}
       '';
 
-      serviceConfig = {
+      unitConfig = {
         ConditionPathExists = "!${cfg.registry.certFile}";
       };
     };
diff --git a/nixos/modules/services/networking/lokinet.nix b/nixos/modules/services/networking/lokinet.nix
new file mode 100644
index 0000000000000..cf091341c8390
--- /dev/null
+++ b/nixos/modules/services/networking/lokinet.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.lokinet;
+  dataDir = "/var/lib/lokinet";
+  settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
+  configFile = settingsFormat.generate "lokinet.ini" (lib.filterAttrsRecursive (n: v: v != null) cfg.settings);
+in with lib; {
+  options.services.lokinet = {
+    enable = mkEnableOption "Lokinet daemon";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.lokinet;
+      defaultText = literalExpression "pkgs.lokinet";
+      description = "Lokinet package to use.";
+    };
+
+    useLocally = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = "Whether to use Lokinet locally.";
+    };
+
+    settings = mkOption {
+      type = with types;
+        submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            dns = {
+              bind = mkOption {
+                type = str;
+                default = "127.3.2.1";
+                description = "Address to bind to for handling DNS requests.";
+              };
+
+              upstream = mkOption {
+                type = listOf str;
+                default = [ "9.9.9.10" ];
+                example = [ "1.1.1.1" "8.8.8.8" ];
+                description = ''
+                  Upstream resolver(s) to use as fallback for non-loki addresses.
+                  Multiple values accepted.
+                '';
+              };
+            };
+
+            network = {
+              exit = mkOption {
+                type = bool;
+                default = false;
+                description = ''
+                  Whether to act as an exit node. Beware that this
+                  increases demand on the server and may pose liability concerns.
+                  Enable at your own risk.
+                '';
+              };
+
+              exit-node = mkOption {
+                type = nullOr (listOf str);
+                default = null;
+                example = ''
+                  exit-node = [ "example.loki" ];              # maps all exit traffic to example.loki
+                  exit-node = [ "example.loki:100.0.0.0/24" ]; # maps 100.0.0.0/24 to example.loki
+                '';
+                description = ''
+                  Specify a `.loki` address and an optional ip range to use as an exit broker.
+                  See <link xlink:href="http://probably.loki/wiki/index.php?title=Exit_Nodes"/> for
+                  a list of exit nodes.
+                '';
+              };
+
+              keyfile = mkOption {
+                type = nullOr str;
+                default = null;
+                example = "snappkey.private";
+                description = ''
+                  The private key to persist address with. If not specified the address will be ephemeral.
+                  This keyfile is generated automatically if the specified file doesn't exist.
+                '';
+              };
+            };
+          };
+        };
+      default = { };
+      example = literalExpression ''
+        {
+          dns = {
+            bind = "127.3.2.1";
+            upstream = [ "1.1.1.1" "8.8.8.8" ];
+          };
+
+          network.exit-node = [ "example.loki" "example2.loki" ];
+        }
+      '';
+      description = ''
+        Configuration for Lokinet.
+        Currently, the best way to view the available settings is by
+        generating a config file using `lokinet -g`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.resolvconf.extraConfig = mkIf cfg.useLocally ''
+      name_servers="${cfg.settings.dns.bind}"
+    '';
+
+    systemd.services.lokinet = {
+      description = "Lokinet";
+      after = [ "network-online.target" "network.target" ];
+      wants = [ "network-online.target" "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        ln -sf ${cfg.package}/share/bootstrap.signed ${dataDir}
+        ${pkgs.coreutils}/bin/install -m 600 ${configFile} ${dataDir}/lokinet.ini
+
+        ${optionalString (cfg.settings.network.keyfile != null) ''
+          ${pkgs.crudini}/bin/crudini --set ${dataDir}/lokinet.ini network keyfile "${dataDir}/${cfg.settings.network.keyfile}"
+        ''}
+      '';
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "lokinet";
+        AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ];
+        ExecStart = "${cfg.package}/bin/lokinet ${dataDir}/lokinet.ini";
+        Restart = "always";
+        RestartSec = "5s";
+
+        # hardening
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateMounts = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        ReadWritePaths = "/dev/net/tun";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix
index 6e8db55bbf0d1..fb3eb71a8ce19 100644
--- a/nixos/modules/services/networking/radvd.nix
+++ b/nixos/modules/services/networking/radvd.nix
@@ -16,9 +16,9 @@ in
 
   ###### interface
 
-  options = {
+  options.services.radvd = {
 
-    services.radvd.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = false;
       description =
@@ -32,7 +32,16 @@ in
         '';
     };
 
-    services.radvd.config = mkOption {
+    package = mkOption {
+      type = types.package;
+      default = pkgs.radvd;
+      defaultText = literalExpression "pkgs.radvd";
+      description = ''
+        The RADVD package to use for the RADVD service.
+      '';
+    };
+
+    config = mkOption {
       type = types.lines;
       example =
         ''
@@ -67,7 +76,7 @@ in
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
         serviceConfig =
-          { ExecStart = "@${pkgs.radvd}/bin/radvd radvd -n -u radvd -C ${confFile}";
+          { ExecStart = "@${cfg.package}/bin/radvd radvd -n -u radvd -C ${confFile}";
             Restart = "always";
           };
       };
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
index b8e2d9a8b0dfc..c1617348fb014 100644
--- a/nixos/modules/services/security/privacyidea.nix
+++ b/nixos/modules/services/security/privacyidea.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.privacyidea;
   opt = options.services.privacyidea;
 
-  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
+  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; };
   python = uwsgi.python3;
   penv = python.withPackages (const [ pkgs.privacyidea ]);
   logCfg = pkgs.writeText "privacyidea-log.cfg" ''
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index d48bc472cb826..4942a05fe73b7 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -7,6 +7,8 @@ let
   opt = options.services.vault;
 
   configFile = pkgs.writeText "vault.hcl" ''
+    # vault in dev mode will refuse to start if its configuration sets listener
+    ${lib.optionalString (!cfg.dev) ''
     listener "tcp" {
       address = "${cfg.address}"
       ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
@@ -17,6 +19,7 @@ let
         ''}
       ${cfg.listenerExtraConfig}
     }
+    ''}
     storage "${cfg.storageBackend}" {
       ${optionalString (cfg.storagePath   != null) ''path = "${cfg.storagePath}"''}
       ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
@@ -30,8 +33,10 @@ let
   '';
 
   allConfigPaths = [configFile] ++ cfg.extraSettingsPaths;
-
-  configOptions = escapeShellArgs (concatMap (p: ["-config" p]) allConfigPaths);
+  configOptions = escapeShellArgs
+    (lib.optional cfg.dev "-dev" ++
+     lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}"
+      ++ (concatMap (p: ["-config" p]) allConfigPaths));
 
 in
 
@@ -47,6 +52,22 @@ in
         description = "This option specifies the vault package to use.";
       };
 
+      dev = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          In this mode, Vault runs in-memory and starts unsealed. This option is not meant production but for development and testing i.e. for nixos tests.
+        '';
+      };
+
+      devRootTokenID = mkOption {
+        type = types.str;
+        default = false;
+        description = ''
+          Initial root token. This only applies when <option>services.vault.dev</option> is true
+        '';
+      };
+
       address = mkOption {
         type = types.str;
         default = "127.0.0.1:8200";
@@ -186,6 +207,9 @@ in
         Group = "vault";
         ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
         ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        StateDirectory = "vault";
+        # In `dev` mode vault will put its token here
+        Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
         PrivateDevices = true;
         PrivateTmp = true;
         ProtectSystem = "full";
diff --git a/nixos/modules/services/system/cachix-agent/default.nix b/nixos/modules/services/system/cachix-agent/default.nix
index f11d80d244d23..3d2e9bc374ba0 100644
--- a/nixos/modules/services/system/cachix-agent/default.nix
+++ b/nixos/modules/services/system/cachix-agent/default.nix
@@ -17,6 +17,12 @@ in {
       defaultText = "config.networking.hostName";
     };
 
+    verbose = mkOption {
+      type = types.bool;
+      description = "Enable verbose output";
+      default = false;
+    };
+
     profile = mkOption {
       type = types.nullOr types.str;
       default = null;
@@ -45,13 +51,16 @@ in {
       after = ["network-online.target"];
       path = [ config.nix.package ];
       wantedBy = [ "multi-user.target" ];
+
       # don't restart while changing
-      reloadIfChanged = true;
+      restartIfChanged = false;
+      unitConfig.X-StopOnRemoval = false;
+
       environment.USER = "root";
       serviceConfig = {
         Restart = "on-failure";
         EnvironmentFile = cfg.credentialsFile;
-        ExecStart = "${cfg.package}/bin/cachix deploy agent ${cfg.name} ${if cfg.profile != null then profile else ""}";
+        ExecStart = "${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} deploy agent ${cfg.name} ${if cfg.profile != null then profile else ""}";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix
index 704cd2cfa8a7c..2bc817343a9b7 100644
--- a/nixos/modules/services/web-apps/calibre-web.nix
+++ b/nixos/modules/services/web-apps/calibre-web.nix
@@ -136,7 +136,7 @@ in
 
               ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}"
             '' + optionalString (cfg.options.calibreLibrary != null) ''
-              test -f ${cfg.options.calibreLibrary}/metadata.db || { echo "Invalid Calibre library"; exit 1; }
+              test -f "${cfg.options.calibreLibrary}/metadata.db" || { echo "Invalid Calibre library"; exit 1; }
             ''
           );
 
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
deleted file mode 100644
index e6772de768e0e..0000000000000
--- a/nixos/modules/services/web-apps/cryptpad.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.cryptpad;
-in
-{
-  options.services.cryptpad = {
-    enable = mkEnableOption "the Cryptpad service";
-
-    package = mkOption {
-      default = pkgs.cryptpad;
-      defaultText = literalExpression "pkgs.cryptpad";
-      type = types.package;
-      description = "
-        Cryptpad package to use.
-      ";
-    };
-
-    configFile = mkOption {
-      type = types.path;
-      default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
-      defaultText = literalExpression ''"''${package}/lib/node_modules/cryptpad/config/config.example.js"'';
-      description = ''
-        Path to the JavaScript configuration file.
-
-        See <link
-        xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/>
-        for a configuration example.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.cryptpad = {
-      description = "Cryptpad Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      serviceConfig = {
-        DynamicUser = true;
-        Environment = [
-          "CRYPTPAD_CONFIG=${cfg.configFile}"
-          "HOME=%S/cryptpad"
-        ];
-        ExecStart = "${cfg.package}/bin/cryptpad";
-        PrivateTmp = true;
-        Restart = "always";
-        StateDirectory = "cryptpad";
-        WorkingDirectory = "%S/cryptpad";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix
new file mode 100644
index 0000000000000..be025e7dd5518
--- /dev/null
+++ b/nixos/modules/services/web-apps/healthchecks.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, buildEnv, ... }:
+
+with lib;
+
+let
+  defaultUser = "healthchecks";
+  cfg = config.services.healthchecks;
+  pkg = cfg.package;
+  boolToPython = b: if b then "True" else "False";
+  environment = {
+    PYTHONPATH = pkg.pythonPath;
+    STATIC_ROOT = cfg.dataDir + "/static";
+    DB_NAME = "${cfg.dataDir}/healthchecks.sqlite";
+  } // cfg.settings;
+
+  environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment);
+
+  healthchecksManageScript = with pkgs; (writeShellScriptBin "healthchecks-manage" ''
+    if [[ "$USER" != "${cfg.user}" ]]; then
+        echo "please run as user 'healtchecks'." >/dev/stderr
+        exit 1
+    fi
+    export $(cat ${environmentFile} | xargs);
+    exec ${pkg}/opt/healthchecks/manage.py "$@"
+  '');
+in
+{
+  options.services.healthchecks = {
+    enable = mkEnableOption "healthchecks" // {
+      description = ''
+        Enable healthchecks.
+        It is expected to be run behind a HTTP reverse proxy.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.healthchecks;
+      defaultText = literalExpression "pkgs.healthchecks";
+      type = types.package;
+      description = "healthchecks package to use.";
+    };
+
+    user = mkOption {
+      default = defaultUser;
+      type = types.str;
+      description = ''
+        User account under which healthchecks runs.
+
+        <note><para>
+        If left as the default value this user will automatically be created
+        on system activation, otherwise you are responsible for
+        ensuring the user exists before the healthchecks service starts.
+        </para></note>
+      '';
+    };
+
+    group = mkOption {
+      default = defaultUser;
+      type = types.str;
+      description = ''
+        Group account under which healthchecks runs.
+
+        <note><para>
+        If left as the default value this group will automatically be created
+        on system activation, otherwise you are responsible for
+        ensuring the group exists before the healthchecks service starts.
+        </para></note>
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "Address the server will listen on.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8000;
+      description = "Port the server will listen on.";
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/healthchecks";
+      description = ''
+        The directory used to store all data for healthchecks.
+
+        <note><para>
+        If left as the default value this directory will automatically be created before
+        the healthchecks server starts, otherwise you are responsible for ensuring the
+        directory exists with appropriate ownership and permissions.
+        </para></note>
+      '';
+    };
+
+    settings = lib.mkOption {
+      description = ''
+        Environment variables which are read by healthchecks <literal>(local)_settings.py</literal>.
+
+        Settings which are explictly covered in options bewlow, are type-checked and/or transformed
+        before added to the environment, everything else is passed as a string.
+
+        See <link xlink:href="">https://healthchecks.io/docs/self_hosted_configuration/</link>
+        for a full documentation of settings.
+
+        We add two variables to this list inside the packages <literal>local_settings.py.</literal>
+        - STATIC_ROOT to set a state directory for dynamically generated static files.
+        - SECRET_KEY_FILE to read SECRET_KEY from a file at runtime and keep it out of /nix/store.
+      '';
+      type = types.submodule {
+        freeformType = types.attrsOf types.str;
+        options = {
+          ALLOWED_HOSTS = lib.mkOption {
+            type = types.listOf types.str;
+            default = [ "*" ];
+            description = "The host/domain names that this site can serve.";
+            apply = lib.concatStringsSep ",";
+          };
+
+          SECRET_KEY_FILE = mkOption {
+            type = types.path;
+            description = "Path to a file containing the secret key.";
+          };
+
+          DEBUG = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Enable debug mode.";
+            apply = boolToPython;
+          };
+
+          REGISTRATION_OPEN = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              A boolean that controls whether site visitors can create new accounts.
+              Set it to false if you are setting up a private Healthchecks instance,
+              but it needs to be publicly accessible (so, for example, your cloud
+              services can send pings to it).
+              If you close new user registration, you can still selectively invite
+              users to your team account.
+            '';
+            apply = boolToPython;
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ healthchecksManageScript ];
+
+    systemd.targets.healthchecks = {
+      description = "Target for all Healthchecks services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+    };
+
+    systemd.services =
+      let
+        commonConfig = {
+          WorkingDirectory = cfg.dataDir;
+          User = cfg.user;
+          Group = cfg.group;
+          EnvironmentFile = environmentFile;
+          StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks";
+          StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
+        };
+      in
+        {
+        healthchecks-migration = {
+          description = "Healthchecks migrations";
+          wantedBy = [ "healthchecks.target" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "on-failure";
+            Type = "oneshot";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py migrate
+            '';
+          };
+        };
+
+        healthchecks = {
+          description = "Healthchecks WSGI Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks-migration.service" ];
+
+          preStart = ''
+            ${pkg}/opt/healthchecks/manage.py collectstatic --no-input
+            ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input
+            ${pkg}/opt/healthchecks/manage.py compress
+          '';
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \
+                --bind ${cfg.listenAddress}:${toString cfg.port} \
+                --pythonpath ${pkg}/opt/healthchecks
+            '';
+          };
+        };
+
+        healthchecks-sendalerts = {
+          description = "Healthchecks Alert Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks.service" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py sendalerts
+            '';
+          };
+        };
+
+        healthchecks-sendreports = {
+          description = "Healthchecks Reporting Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks.service" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py sendreports --loop
+            '';
+          };
+        };
+      };
+
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        {
+          description = "healthchecks service owner";
+          isSystemUser = true;
+          group = defaultUser;
+        };
+    };
+
+    users.groups = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        {
+          members = [ defaultUser ];
+        };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index be0b5b94fb26c..8ad92706b0647 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -253,9 +253,20 @@ in
         '';
       };
     };
-    systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable {
-      EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
-      SupplementaryGroups = [ "jitsi-meet" ];
+    systemd.services.prosody = mkIf cfg.prosody.enable {
+      preStart = let
+        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
+      in ''
+        ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
+        ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
+        ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
+        ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
+        ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
+      '';
+      serviceConfig = {
+        EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
+        SupplementaryGroups = [ "jitsi-meet" ];
+      };
     };
 
     users.groups.jitsi-meet = {};
@@ -266,14 +277,12 @@ in
     systemd.services.jitsi-meet-init-secrets = {
       wantedBy = [ "multi-user.target" ];
       before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service");
-      path = [ config.services.prosody.package ];
       serviceConfig = {
         Type = "oneshot";
       };
 
       script = let
         secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
-        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
       in
       ''
         cd /var/lib/jitsi-meet
@@ -291,12 +300,6 @@ in
         chmod 640 secrets-env
       ''
       + optionalString cfg.prosody.enable ''
-        prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
-        prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
-        prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
-        prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
-        prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
-
         # generate self-signed certificates
         if [ ! -f /var/lib/jitsi-meet.crt ]; then
           ${getBin pkgs.openssl}/bin/openssl req \
diff --git a/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix
new file mode 100644
index 0000000000000..f0e97da1f2023
--- /dev/null
+++ b/nixos/modules/services/web-apps/phylactery.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let cfg = config.services.phylactery;
+in {
+  options.services.phylactery = {
+    enable = mkEnableOption "Whether to enable Phylactery server";
+
+    host = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "Listen host for Phylactery";
+    };
+
+    port = mkOption {
+      type = types.port;
+      description = "Listen port for Phylactery";
+    };
+
+    library = mkOption {
+      type = types.path;
+      description = "Path to CBZ library";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.phylactery;
+      defaultText = literalExpression "pkgs.phylactery";
+      description = "The Phylactery package to use";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.phylactery = {
+      environment = {
+        PHYLACTERY_ADDRESS = "${cfg.host}:${toString cfg.port}";
+        PHYLACTERY_LIBRARY = "${cfg.library}";
+      };
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ConditionPathExists = cfg.library;
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/phylactery";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ McSinyx ];
+}
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 2c2f2cae4b749..ffdf7e9a86eb5 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -72,7 +72,9 @@ in
         apply = map (d: d // {
           manage = "desktop";
           start = d.start
-          + optionalString (needBGCond d) ''\n\n
+          # literal newline to ensure d.start's last line is not appended to
+          + optionalString (needBGCond d) ''
+
             if [ -e $HOME/.background-image ]; then
               ${pkgs.feh}/bin/feh --bg-${cfg.wallpaper.mode} ${optionalString cfg.wallpaper.combineScreens "--no-xinerama"} $HOME/.background-image
             fi
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 3c2dac386f5db..3a468b550627f 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -4,10 +4,9 @@ with lib;
 
 let
   cfg = config.services.xserver.desktopManager.xfce;
-in
 
+in
 {
-
   meta = {
     maintainers = teams.xfce.members;
   };
@@ -95,7 +94,6 @@ in
       exo
       garcon
       libxfce4ui
-      xfconf
 
       mousepad
       parole
@@ -125,6 +123,7 @@ in
         xfdesktop
       ] ++ optional cfg.enableScreensaver xfce4-screensaver;
 
+    programs.xfconf.enable = true;
     programs.thunar.enable = true;
 
     environment.pathsToLink = [
diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix
index 4951aef95584b..0b1e6277c2f5c 100644
--- a/nixos/modules/system/boot/systemd/user.nix
+++ b/nixos/modules/system/boot/systemd/user.nix
@@ -14,6 +14,7 @@ let
     generateUnits
     targetToUnit
     serviceToUnit
+    sliceToUnit
     socketToUnit
     timerToUnit
     pathToUnit;
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index c8bbfe9769b21..05174e03754b6 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -549,7 +549,7 @@ in
                 zfs load-key -a
               ''
               else concatMapStrings (fs: ''
-                zfs load-key ${fs}
+                zfs load-key -- ${escapeShellArg fs}
               '') cfgZfs.requestEncryptionCredentials}
         '') rootPools));
 
@@ -701,7 +701,7 @@ in
           # expand every pool. Otherwise we want to enumerate
           # just the specifically provided list of pools.
           poolListProvider = if cfgExpandOnBoot == "all"
-            then "$(zpool list -H | awk '{print $1}')"
+            then "$(zpool list -H -o name)"
             else lib.escapeShellArgs cfgExpandOnBoot;
         in
         {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index f622897aa6207..e87f540fd57cb 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -684,6 +684,21 @@ in
           '';
       };
 
+    virtualisation.useDefaultFilesystems =
+      mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            If enabled, the boot disk of the virtual machine will be
+            formatted and mounted with the default filesystems for
+            testing. Swap devices and LUKS will be disabled.
+
+            If disabled, a root filesystem has to be specified and
+            formatted (for example in the initial ramdisk).
+          '';
+      };
+
     virtualisation.efiVars =
       mkOption {
         type = types.str;
@@ -754,13 +769,13 @@ in
     );
     boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
 
-    boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
+    boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # We need mke2fs in the initrd.
         copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs
       '';
 
-    boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
+    boot.initrd.postDeviceCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # If the disk image appears to be empty, run mke2fs to
         # initialise.
@@ -930,38 +945,41 @@ in
         };
     in
       mkVMOverride (cfg.fileSystems //
-      {
+      optionalAttrs cfg.useDefaultFilesystems {
         "/".device = cfg.bootDevice;
         "/".fsType = "ext4";
         "/".autoFormat = true;
-
-        "/tmp" = mkIf config.boot.tmpOnTmpfs
-          { device = "tmpfs";
-            fsType = "tmpfs";
-            neededForBoot = true;
-            # Sync with systemd's tmp.mount;
-            options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
-          };
-
-        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" =
-          mkIf cfg.useNixStoreImage
-            { device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
-              neededForBoot = true;
-              options = [ "ro" ];
-            };
-
-        "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs)
-          { fsType = "tmpfs";
-            options = [ "mode=0755" ];
-            neededForBoot = true;
-          };
-
-        "/boot" = mkIf cfg.useBootLoader
-          # see note [Disk layout with `useBootLoader`]
-          { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
-            fsType = "vfat";
-            noCheck = true; # fsck fails on a r/o filesystem
-          };
+      } //
+      optionalAttrs config.boot.tmpOnTmpfs {
+        "/tmp" = {
+          device = "tmpfs";
+          fsType = "tmpfs";
+          neededForBoot = true;
+          # Sync with systemd's tmp.mount;
+          options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
+        };
+      } //
+      optionalAttrs cfg.useNixStoreImage {
+        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = {
+          device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
+          neededForBoot = true;
+          options = [ "ro" ];
+        };
+      } //
+      optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) {
+        "/nix/.rw-store" = {
+          fsType = "tmpfs";
+          options = [ "mode=0755" ];
+          neededForBoot = true;
+        };
+      } //
+      optionalAttrs cfg.useBootLoader {
+        # see note [Disk layout with `useBootLoader`]
+        "/boot" = {
+          device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
+          fsType = "vfat";
+          noCheck = true; # fsck fails on a r/o filesystem
+        };
       } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
 
     boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
@@ -986,8 +1004,8 @@ in
       };
     };
 
-    swapDevices = mkVMOverride [ ];
-    boot.initrd.luks.devices = mkVMOverride {};
+    swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
+    boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {};
 
     # Don't run ntpd in the guest.  It should get the correct time from KVM.
     services.timesyncd.enable = false;
diff --git a/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix
deleted file mode 100644
index e719e25650d37..0000000000000
--- a/nixos/modules/virtualisation/railcar.nix
+++ /dev/null
@@ -1,124 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.railcar;
-  generateUnit = name: containerConfig:
-    let
-      container = pkgs.ociTools.buildContainer {
-        args = [
-          (pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath
-        ];
-      };
-    in
-      nameValuePair "railcar-${name}" {
-        enable = true;
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-            ExecStart = ''
-              ${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container}
-            '';
-            Type = containerConfig.runType;
-          };
-      };
-  mount = with types; (submodule {
-    options = {
-      type = mkOption {
-        type = str;
-        default = "none";
-        description = ''
-          The type of the filesystem to be mounted.
-          Linux: filesystem types supported by the kernel as listed in
-          `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs",
-          "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts
-          (when options include either bind or rbind), the type is a dummy,
-          often "none" (not listed in /proc/filesystems).
-        '';
-      };
-      source = mkOption {
-        type = str;
-        description = "Source for the in-container mount";
-      };
-      options = mkOption {
-        type = listOf str;
-        default = [ "bind" ];
-        description = ''
-          Mount options of the filesystem to be used.
-
-          Support options are listed in the mount(8) man page. Note that
-          both filesystem-independent and filesystem-specific options
-          are listed.
-        '';
-      };
-    };
-  });
-in
-{
-  options.services.railcar = {
-    enable = mkEnableOption "railcar";
-
-    containers = mkOption {
-      default = {};
-      description = "Declarative container configuration";
-      type = with types; attrsOf (submodule ({ name, config, ... }: {
-        options = {
-          cmd = mkOption {
-            type = types.lines;
-            description = "Command or script to run inside the container";
-          };
-
-          mounts = mkOption {
-            type = with types; attrsOf mount;
-            default = {};
-            description = ''
-              A set of mounts inside the container.
-
-              The defaults have been chosen for simple bindmounts, meaning
-              that you only need to provide the "source" parameter.
-            '';
-            example = { "/data" = { source = "/var/lib/data"; }; };
-          };
-
-          runType = mkOption {
-            type = types.str;
-            default = "oneshot";
-            description = "The systemd service run type";
-          };
-
-          os = mkOption {
-            type = types.str;
-            default = "linux";
-            description = "OS type of the container";
-          };
-
-          arch = mkOption {
-            type = types.str;
-            default = "x86_64";
-            description = "Computer architecture type of the container";
-          };
-        };
-      }));
-    };
-
-    stateDir = mkOption {
-      type = types.path;
-      default = "/var/railcar";
-      description = "Railcar persistent state directory";
-    };
-
-    package = mkOption {
-      type = types.package;
-      default = pkgs.railcar;
-      defaultText = literalExpression "pkgs.railcar";
-      description = "Railcar package to use";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services = flip mapAttrs' cfg.containers (name: containerConfig:
-      generateUnit name containerConfig
-    );
-  };
-}
-
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 671b1a8876a05..d596db8ab7467 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -110,7 +110,6 @@ in {
   cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
-  cryptpad = handleTest ./cryptpad.nix {};
   deluge = handleTest ./deluge.nix {};
   dendrite = handleTest ./matrix/dendrite.nix {};
   dex-oidc = handleTest ./dex-oidc.nix {};
@@ -203,6 +202,7 @@ in {
   haste-server = handleTest ./haste-server.nix {};
   haproxy = handleTest ./haproxy.nix {};
   hardened = handleTest ./hardened.nix {};
+  healthchecks = handleTest ./web-apps/healthchecks.nix {};
   hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; };
   hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
   hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
@@ -383,6 +383,7 @@ in {
   nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
   node-red = handleTest ./node-red.nix {};
   nomad = handleTest ./nomad.nix {};
+  non-default-filesystems = handleTest ./non-default-filesystems.nix {};
   noto-fonts = handleTest ./noto-fonts.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
   nsd = handleTest ./nsd.nix {};
@@ -425,6 +426,7 @@ in {
   php = handleTest ./php {};
   php80 = handleTest ./php { php = pkgs.php80; };
   php81 = handleTest ./php { php = pkgs.php81; };
+  phylactery = handleTest ./web-apps/phylactery.nix {};
   pict-rs = handleTest ./pict-rs.nix {};
   pinnwand = handleTest ./pinnwand.nix {};
   plasma5 = handleTest ./plasma5.nix {};
@@ -517,6 +519,7 @@ in {
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   sudo = handleTest ./sudo.nix {};
+  swap-partition = handleTest ./swap-partition.nix {};
   sway = handleTest ./sway.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
@@ -586,6 +589,7 @@ in {
   uwsgi = handleTest ./uwsgi.nix {};
   v2ray = handleTest ./v2ray.nix {};
   vault = handleTest ./vault.nix {};
+  vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
   vector = handleTest ./vector.nix {};
diff --git a/nixos/tests/cryptpad.nix b/nixos/tests/cryptpad.nix
deleted file mode 100644
index db271f937ef42..0000000000000
--- a/nixos/tests/cryptpad.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-import ./make-test-python.nix ({ lib, ... }:
-
-with lib;
-
-{
-  name = "cryptpad";
-  meta.maintainers = with maintainers; [ davhau ];
-
-  nodes.machine =
-    { pkgs, ... }:
-    { services.cryptpad.enable = true; };
-
-  testScript = ''
-    machine.wait_for_unit("cryptpad.service")
-    machine.wait_for_open_port(3000)
-    machine.succeed("curl -L --fail http://localhost:3000/sheet")
-  '';
-})
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index c6fb37cfe5842..b81384aa8c0b5 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -92,6 +92,7 @@ in
   fwupd = callInstalledTest ./fwupd.nix {};
   gcab = callInstalledTest ./gcab.nix {};
   gdk-pixbuf = callInstalledTest ./gdk-pixbuf.nix {};
+  geocode-glib = callInstalledTest ./geocode-glib.nix {};
   gjs = callInstalledTest ./gjs.nix {};
   glib-networking = callInstalledTest ./glib-networking.nix {};
   gnome-photos = callInstalledTest ./gnome-photos.nix {};
diff --git a/nixos/tests/installed-tests/geocode-glib.nix b/nixos/tests/installed-tests/geocode-glib.nix
new file mode 100644
index 0000000000000..fcb38c96ab0f6
--- /dev/null
+++ b/nixos/tests/installed-tests/geocode-glib.nix
@@ -0,0 +1,13 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  testConfig = {
+    i18n.supportedLocales = [
+      "en_US.UTF-8/UTF-8"
+      # The tests require this locale available.
+      "en_GB.UTF-8/UTF-8"
+    ];
+  };
+
+  tested = pkgs.geocode-glib;
+}
diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
index 41d53bc73800e..c39cd19e1f0a7 100644
--- a/nixos/tests/jitsi-meet.nix
+++ b/nixos/tests/jitsi-meet.nix
@@ -34,9 +34,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_unit("prosody.service")
 
     server.wait_until_succeeds(
-        "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'"
-    )
-    server.wait_until_succeeds(
         "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'"
     )
     server.wait_until_succeeds(
diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix
index 581748c1fa59b..b9d0416482da1 100644
--- a/nixos/tests/maddy.nix
+++ b/nixos/tests/maddy.nix
@@ -49,7 +49,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_open_port(143)
     server.wait_for_open_port(587)
 
-    server.succeed("echo test | maddyctl creds create postmaster@server")
+    server.succeed("maddyctl creds create --password test postmaster@server")
     server.succeed("maddyctl imap-acct create postmaster@server")
 
     client.succeed("send-testmail")
diff --git a/nixos/tests/nginx-http3.nix b/nixos/tests/nginx-http3.nix
index edd0759464c8a..319f6aac184ab 100644
--- a/nixos/tests/nginx-http3.nix
+++ b/nixos/tests/nginx-http3.nix
@@ -70,6 +70,9 @@ in
   testScript = ''
     start_all()
 
+    server.wait_for_unit("nginx")
+    server.wait_for_open_port(443)
+
     # Check http connections
     client.succeed("curl --verbose --http3 https://acme.test | grep 'Hello World!'")
 
diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix
new file mode 100644
index 0000000000000..7fa75aaad724d
--- /dev/null
+++ b/nixos/tests/non-default-filesystems.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "non-default-filesystems";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    let
+      disk = config.virtualisation.bootDevice;
+    in
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      boot.initrd.availableKernelModules = [ "btrfs" ];
+      boot.supportedFilesystems = [ "btrfs" ];
+
+      boot.initrd.postDeviceCommands = ''
+        FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
+        if test -z "$FSTYPE"; then
+          modprobe btrfs
+          ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
+
+          mkdir /nixos
+          mount -t btrfs ${disk} /nixos
+
+          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
+          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+
+          umount /nixos
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = disk;
+          fsType = "btrfs";
+          options = [ "subvol=/root" ];
+        };
+
+        "/home" = {
+          device = disk;
+          fsType = "btrfs";
+          options = [ "subvol=/home" ];
+        };
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("BTRFS filesystems are mounted correctly"):
+      machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
+      machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
+  '';
+})
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 849f5ee159f0c..a3092d101d87e 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -1335,7 +1335,7 @@ mapAttrs
       '';
 
       meta = with maintainers; {
-        maintainers = [ willibutz elseym ];
+        maintainers = [ willibutz ];
       };
     }
   )))
diff --git a/nixos/tests/swap-partition.nix b/nixos/tests/swap-partition.nix
new file mode 100644
index 0000000000000..2279630b57b8f
--- /dev/null
+++ b/nixos/tests/swap-partition.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-partition";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.bootDevice = "/dev/vda1";
+
+      boot.initrd.postDeviceCommands = ''
+        if ! test -b /dev/vda1; then
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100%
+          sync
+        fi
+
+        FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true)
+        if test -z "$FSTYPE"; then
+          ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1
+          ${pkgs.util-linux}/bin/mkswap --label swap /dev/vda2
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/disk/by-label/swap";
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Swap is active"):
+      # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions.
+      machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'")
+  '';
+})
diff --git a/nixos/tests/vault-dev.nix b/nixos/tests/vault-dev.nix
new file mode 100644
index 0000000000000..ba9a1015cc13c
--- /dev/null
+++ b/nixos/tests/vault-dev.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "vault-dev";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 mic92 ];
+  };
+  nodes.machine = { pkgs, config, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    environment.variables.VAULT_TOKEN = "phony-secret";
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+  };
+
+  testScript = ''
+    import json
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+    out = machine.succeed("vault status -format=json")
+    print(out)
+    status = json.loads(out)
+    assert status.get("initialized") == True
+    machine.succeed("vault kv put secret/foo bar=baz")
+    out = machine.succeed("vault kv get -format=json secret/foo")
+    print(out)
+    status = json.loads(out)
+    assert status.get("data", {}).get("data", {}).get("bar") == "baz"
+  '';
+})
diff --git a/nixos/tests/web-apps/healthchecks.nix b/nixos/tests/web-apps/healthchecks.nix
new file mode 100644
index 0000000000000..41374f5e314bb
--- /dev/null
+++ b/nixos/tests/web-apps/healthchecks.nix
@@ -0,0 +1,42 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "healthchecks";
+
+  meta = with lib.maintainers; {
+    maintainers = [ phaer ];
+  };
+
+  nodes.machine = { ... }: {
+    services.healthchecks = {
+      enable = true;
+      settings = {
+        SITE_NAME = "MyUniqueInstance";
+        COMPRESS_ENABLED = "True";
+        SECRET_KEY_FILE = pkgs.writeText "secret"
+          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("healthchecks.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit healthchecks --grep Listening")
+
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep '<title>Sign In'"
+        )
+
+    with subtest("Setting SITE_NAME via freeform option works"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep 'MyUniqueInstance</title>'"
+        )
+
+    with subtest("Manage script works"):
+        # Should fail if not called by healthchecks user
+        machine.fail("echo 'print(\"foo\")' | healthchecks-manage help")
+
+        # "shell" sucommand should succeed, needs python in PATH.
+        assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | sudo -u healthchecks healthchecks-manage shell")
+  '';
+})
diff --git a/nixos/tests/web-apps/phylactery.nix b/nixos/tests/web-apps/phylactery.nix
new file mode 100644
index 0000000000000..cf2689d2300d3
--- /dev/null
+++ b/nixos/tests/web-apps/phylactery.nix
@@ -0,0 +1,20 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "phylactery";
+
+  nodes.machine = { ... }: {
+    services.phylactery = rec {
+      enable = true;
+      port = 8080;
+      library = "/tmp";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit('phylactery')
+    machine.wait_for_open_port(8080)
+    machine.wait_until_succeeds('curl localhost:8080')
+  '';
+
+  meta.maintainers = with lib.maintainers; [ McSinyx ];
+})
diff --git a/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix
index 0e1d521c5aced..f277d4b795256 100644
--- a/nixos/tests/xrdp.nix
+++ b/nixos/tests/xrdp.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "xrdp";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ volth ];
+    maintainers = [ ];
   };
 
   nodes = {