about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md26
-rw-r--r--nixos/doc/manual/development/option-types.section.md13
-rw-r--r--nixos/doc/manual/development/settings-options.section.md45
-rw-r--r--nixos/doc/manual/development/unit-handling.section.md15
-rw-r--r--nixos/doc/manual/from_md/development/option-declarations.section.xml29
-rw-r--r--nixos/doc/manual/from_md/development/option-types.section.xml22
-rw-r--r--nixos/doc/manual/from_md/development/settings-options.section.xml104
-rw-r--r--nixos/doc/manual/from_md/development/unit-handling.section.xml22
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2205.section.xml240
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md120
-rw-r--r--nixos/lib/utils.nix5
-rw-r--r--nixos/modules/hardware/all-firmware.nix1
-rw-r--r--nixos/modules/hardware/xone.nix23
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64.nix6
-rw-r--r--nixos/modules/misc/documentation.nix2
-rw-r--r--nixos/modules/misc/ids.nix4
-rw-r--r--nixos/modules/misc/version.nix51
-rw-r--r--nixos/modules/module-list.nix10
-rw-r--r--nixos/modules/programs/firejail.nix4
-rw-r--r--nixos/modules/programs/kclock.nix13
-rw-r--r--nixos/modules/programs/phosh.nix19
-rw-r--r--nixos/modules/programs/sway.nix1
-rw-r--r--nixos/modules/rename.nix1
-rw-r--r--nixos/modules/security/pam.nix2
-rw-r--r--nixos/modules/security/polkit.nix6
-rw-r--r--nixos/modules/security/systemd-confinement.nix4
-rw-r--r--nixos/modules/services/admin/pgadmin.nix127
-rw-r--r--nixos/modules/services/audio/snapserver.nix26
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.nix2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json118
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json65
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json7
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix5
-rw-r--r--nixos/modules/services/development/zammad.nix323
-rw-r--r--nixos/modules/services/hardware/udisks2.nix2
-rw-r--r--nixos/modules/services/home-automation/zigbee2mqtt.nix (renamed from nixos/modules/services/misc/zigbee2mqtt.nix)0
-rw-r--r--nixos/modules/services/logging/logrotate.nix1
-rw-r--r--nixos/modules/services/matrix/matrix-synapse-log_config.yaml (renamed from nixos/modules/services/misc/matrix-synapse-log_config.yaml)0
-rw-r--r--nixos/modules/services/matrix/matrix-synapse.nix773
-rw-r--r--nixos/modules/services/matrix/matrix-synapse.xml (renamed from nixos/modules/services/misc/matrix-synapse.xml)35
-rw-r--r--nixos/modules/services/misc/input-remapper.nix27
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix844
-rw-r--r--nixos/modules/services/misc/nitter.nix15
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix34
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix2
-rw-r--r--nixos/modules/services/networking/bird.nix182
-rw-r--r--nixos/modules/services/networking/murmur.nix2
-rw-r--r--nixos/modules/services/networking/networkmanager.nix1
-rw-r--r--nixos/modules/services/networking/snowflake-proxy.nix81
-rw-r--r--nixos/modules/services/security/clamav.nix9
-rw-r--r--nixos/modules/services/security/fprot.nix82
-rw-r--r--nixos/modules/services/security/opensnitch.nix103
-rw-r--r--nixos/modules/services/system/earlyoom.nix35
-rw-r--r--nixos/modules/services/wayland/cage.nix2
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix10
-rw-r--r--nixos/modules/services/web-apps/peertube.nix12
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix3
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl9
-rw-r--r--nixos/modules/system/boot/systemd.nix2
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix5
-rw-r--r--nixos/modules/virtualisation/proxmox-lxc.nix64
-rw-r--r--nixos/release-combined.nix4
-rw-r--r--nixos/release-small.nix6
-rw-r--r--nixos/tests/all-tests.nix12
-rw-r--r--nixos/tests/bird.nix132
-rw-r--r--nixos/tests/cntr.nix20
-rw-r--r--nixos/tests/home-assistant.nix43
-rw-r--r--nixos/tests/input-remapper.nix52
-rw-r--r--nixos/tests/keycloak.nix2
-rw-r--r--nixos/tests/kubernetes/base.nix4
-rw-r--r--nixos/tests/kubernetes/rbac.nix4
-rw-r--r--nixos/tests/logrotate.nix20
-rw-r--r--nixos/tests/matrix-appservice-irc.nix46
-rw-r--r--nixos/tests/matrix-synapse.nix64
-rw-r--r--nixos/tests/matrix/mjolnir.nix43
-rw-r--r--nixos/tests/matrix/pantalaimon.nix29
-rw-r--r--nixos/tests/nano.nix44
-rw-r--r--nixos/tests/nats.nix24
-rw-r--r--nixos/tests/networking.nix2
-rw-r--r--nixos/tests/nixops/default.nix2
-rw-r--r--nixos/tests/pgadmin4-standalone.nix43
-rw-r--r--nixos/tests/pgadmin4.nix142
-rw-r--r--nixos/tests/podman/default.nix2
-rw-r--r--nixos/tests/podman/tls-ghostunnel.nix2
-rw-r--r--nixos/tests/shiori.nix1
-rw-r--r--nixos/tests/switch-test.nix198
-rw-r--r--nixos/tests/systemd-confinement.nix29
-rw-r--r--nixos/tests/tinywl.nix1
-rw-r--r--nixos/tests/vsftpd.nix42
-rw-r--r--nixos/tests/zammad.nix60
93 files changed, 3330 insertions, 1545 deletions
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index fff06e1ea5ba..819fc6d891f0 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -145,26 +145,26 @@ As an example, we will take the case of display managers. There is a
 central display manager module for generic display manager options and a
 module file per display manager backend (sddm, gdm \...).
 
-There are two approach to this module structure:
+There are two approaches we could take with this module structure:
 
--   Managing the display managers independently by adding an enable
+-   Configuring the display managers independently by adding an enable
     option to every display manager module backend. (NixOS)
 
--   Managing the display managers in the central module by adding an
-    option to select which display manager backend to use.
+-   Configuring the display managers in the central module by adding
+    an option to select which display manager backend to use.
 
 Both approaches have problems.
 
 Making backends independent can quickly become hard to manage. For
-display managers, there can be only one enabled at a time, but the type
-system can not enforce this restriction as there is no relation between
-each backend `enable` option. As a result, this restriction has to be
-done explicitely by adding assertions in each display manager backend
-module.
+display managers, there can only be one enabled at a time, but the
+type system cannot enforce this restriction as there is no relation
+between each backend's `enable` option. As a result, this restriction
+has to be done explicitly by adding assertions in each display manager
+backend module.
 
-On the other hand, managing the display managers backends in the central
-module will require to change the central module option every time a new
-backend is added or removed.
+On the other hand, managing the display manager backends in the
+central module will require changing the central module option every
+time a new backend is added or removed.
 
 By using extensible option types, it is possible to create a placeholder
 option in the central module
@@ -175,7 +175,7 @@ and to extend it in each backend module
 
 As a result, `displayManager.enable` option values can be added without
 changing the main service module file and the type system automatically
-enforce that there can only be a single display manager enabled.
+enforces that there can only be a single display manager enabled.
 
 ::: {#ex-option-declaration-eot-service .example}
 ::: {.title}
diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md
index 78ace62e8f17..c34ac0367c4f 100644
--- a/nixos/doc/manual/development/option-types.section.md
+++ b/nixos/doc/manual/development/option-types.section.md
@@ -16,9 +16,9 @@ merging is handled.
 
 `types.path`
 
-:   A filesystem path, defined as anything that when coerced to a string
-    starts with a slash. Even if derivations can be considered as path,
-    the more specific `types.package` should be preferred.
+:   A filesystem path is anything that starts with a slash when
+    coerced to a string. Even if derivations can be considered as
+    paths, the more specific `types.package` should be preferred.
 
 `types.package`
 
@@ -74,6 +74,13 @@ merging is handled.
     should only be used when checking, merging and nested evaluation are not
     desirable.
 
+`types.optionType`
+
+:   The type of an option's type. Its merging operation ensures that nested
+    options have the correct file location annotated, and that if possible,
+    multiple option definitions are correctly merged together. The main use
+    case is as the type of the `_module.freeformType` option.
+
 `types.attrs`
 
 :   A free-form attribute set.
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 58a3d8448af5..f9bb6ff9cc41 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -66,6 +66,45 @@ have a predefined type and string generator already declared under
     and returning a set with TOML-specific attributes `type` and
     `generate` as specified [below](#pkgs-formats-result).
 
+`pkgs.formats.elixirConf { elixir ? pkgs.elixir }`
+
+:   A function taking an attribute set with values
+
+    `elixir`
+
+    :   The Elixir package which will be used to format the generated output
+
+    It returns a set with Elixir-Config-specific attributes `type`, `lib`, and
+    `generate` as specified [below](#pkgs-formats-result).
+
+    The `lib` attribute contains functions to be used in settings, for
+    generating special Elixir values:
+
+    `mkRaw elixirCode`
+
+    :   Outputs the given string as raw Elixir code
+
+    `mkGetEnv { envVariable, fallback ? null }`
+
+    :   Makes the configuration fetch an environment variable at runtime
+
+    `mkAtom atom`
+
+    :   Outputs the given string as an Elixir atom, instead of the default
+        Elixir binary string. Note: lowercase atoms still needs to be prefixed
+        with `:`
+
+    `mkTuple array`
+
+    :   Outputs the given array as an Elixir tuple, instead of the default
+        Elixir list
+
+    `mkMap attrset`
+
+    :   Outputs the given attribute set as an Elixir map, instead of the
+        default Elixir keyword list
+
+
 ::: {#pkgs-formats-result}
 These functions all return an attribute set with these values:
 :::
@@ -74,6 +113,12 @@ These functions all return an attribute set with these values:
 
 :   A module system type representing a value of the format
 
+`lib`
+
+:   Utility functions for convenience, or special interactions with the format.
+    This attribute is optional. It may contain inside a `types` attribute
+    containing types specific to this format.
+
 `generate` *`filename jsonValue`*
 
 :   A function that can render a value of the format to a file. Returns
diff --git a/nixos/doc/manual/development/unit-handling.section.md b/nixos/doc/manual/development/unit-handling.section.md
index d477f2c860f3..bd4fe9e670f5 100644
--- a/nixos/doc/manual/development/unit-handling.section.md
+++ b/nixos/doc/manual/development/unit-handling.section.md
@@ -41,17 +41,18 @@ checks:
     `RefuseManualStop` in the `[Unit]` section, and `X-OnlyManualStart` in the
     `[Unit]` section.
 
-  - The rest of the behavior is decided whether the unit has `X-StopIfChanged`
-    in the `[Service]` section set (exposed via
+  - Further behavior depends on the unit having `X-StopIfChanged` in the
+    `[Service]` section set to `true` (exposed via
     [systemd.services.\<name\>.stopIfChanged](#opt-systemd.services)). This is
     set to `true` by default and must be explicitly turned off if not wanted.
     If the flag is enabled, the unit is **stop**ped and then **start**ed. If
     not, the unit is **restart**ed. The goal of the flag is to make sure that
     the new unit never runs in the old environment which is still in place
-    before the activation script is run.
+    before the activation script is run. This behavior is different when the
+    service is socket-activated, as outlined in the following steps.
 
   - The last thing that is taken into account is whether the unit is a service
-    and socket-activated. Due to a bug, this is currently only done when
-    `X-StopIfChanged` is set. If the unit is socket-activated, the socket is
-    stopped and started, and the service is stopped and to be started by socket
-    activation.
+    and socket-activated. If `X-StopIfChanged` is **not** set, the service
+    is **restart**ed with the others. If it is set, both the service and the
+    socket are **stop**ped and the socket is **start**ed, leaving socket
+    activation to start the service when it's needed.
diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml
index 0eeffae628e1..554705e2e424 100644
--- a/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml
@@ -215,21 +215,22 @@ lib.mkOption {
             manager backend (sddm, gdm ...).
           </para>
           <para>
-            There are two approach to this module structure:
+            There are two approaches we could take with this module
+            structure:
           </para>
           <itemizedlist>
             <listitem>
               <para>
-                Managing the display managers independently by adding an
-                enable option to every display manager module backend.
-                (NixOS)
+                Configuring the display managers independently by adding
+                an enable option to every display manager module
+                backend. (NixOS)
               </para>
             </listitem>
             <listitem>
               <para>
-                Managing the display managers in the central module by
-                adding an option to select which display manager backend
-                to use.
+                Configuring the display managers in the central module
+                by adding an option to select which display manager
+                backend to use.
               </para>
             </listitem>
           </itemizedlist>
@@ -238,16 +239,16 @@ lib.mkOption {
           </para>
           <para>
             Making backends independent can quickly become hard to
-            manage. For display managers, there can be only one enabled
-            at a time, but the type system can not enforce this
-            restriction as there is no relation between each backend
+            manage. For display managers, there can only be one enabled
+            at a time, but the type system cannot enforce this
+            restriction as there is no relation between each backend’s
             <literal>enable</literal> option. As a result, this
-            restriction has to be done explicitely by adding assertions
+            restriction has to be done explicitly by adding assertions
             in each display manager backend module.
           </para>
           <para>
-            On the other hand, managing the display managers backends in
-            the central module will require to change the central module
+            On the other hand, managing the display manager backends in
+            the central module will require changing the central module
             option every time a new backend is added or removed.
           </para>
           <para>
@@ -268,7 +269,7 @@ lib.mkOption {
           <para>
             As a result, <literal>displayManager.enable</literal> option
             values can be added without changing the main service module
-            file and the type system automatically enforce that there
+            file and the type system automatically enforces that there
             can only be a single display manager enabled.
           </para>
           <anchor xml:id="ex-option-declaration-eot-service" />
diff --git a/nixos/doc/manual/from_md/development/option-types.section.xml b/nixos/doc/manual/from_md/development/option-types.section.xml
index 90ef05a24e77..e16453df51e6 100644
--- a/nixos/doc/manual/from_md/development/option-types.section.xml
+++ b/nixos/doc/manual/from_md/development/option-types.section.xml
@@ -30,10 +30,10 @@
         </term>
         <listitem>
           <para>
-            A filesystem path, defined as anything that when coerced to
-            a string starts with a slash. Even if derivations can be
-            considered as path, the more specific
-            <literal>types.package</literal> should be preferred.
+            A filesystem path is anything that starts with a slash when
+            coerced to a string. Even if derivations can be considered
+            as paths, the more specific <literal>types.package</literal>
+            should be preferred.
           </para>
         </listitem>
       </varlistentry>
@@ -113,6 +113,20 @@
       </varlistentry>
       <varlistentry>
         <term>
+          <literal>types.optionType</literal>
+        </term>
+        <listitem>
+          <para>
+            The type of an option’s type. Its merging operation ensures
+            that nested options have the correct file location
+            annotated, and that if possible, multiple option definitions
+            are correctly merged together. The main use case is as the
+            type of the <literal>_module.freeformType</literal> option.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <literal>types.attrs</literal>
         </term>
         <listitem>
diff --git a/nixos/doc/manual/from_md/development/settings-options.section.xml b/nixos/doc/manual/from_md/development/settings-options.section.xml
index c9430b77579c..746011a2d075 100644
--- a/nixos/doc/manual/from_md/development/settings-options.section.xml
+++ b/nixos/doc/manual/from_md/development/settings-options.section.xml
@@ -137,6 +137,97 @@
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <literal>pkgs.formats.elixirConf { elixir ? pkgs.elixir }</literal>
+        </term>
+        <listitem>
+          <para>
+            A function taking an attribute set with values
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <literal>elixir</literal>
+              </term>
+              <listitem>
+                <para>
+                  The Elixir package which will be used to format the
+                  generated output
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+          <para>
+            It returns a set with Elixir-Config-specific attributes
+            <literal>type</literal>, <literal>lib</literal>, and
+            <literal>generate</literal> as specified
+            <link linkend="pkgs-formats-result">below</link>.
+          </para>
+          <para>
+            The <literal>lib</literal> attribute contains functions to
+            be used in settings, for generating special Elixir values:
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <literal>mkRaw elixirCode</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given string as raw Elixir code
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkGetEnv { envVariable, fallback ? null }</literal>
+              </term>
+              <listitem>
+                <para>
+                  Makes the configuration fetch an environment variable
+                  at runtime
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkAtom atom</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given string as an Elixir atom, instead of
+                  the default Elixir binary string. Note: lowercase
+                  atoms still needs to be prefixed with
+                  <literal>:</literal>
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkTuple array</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given array as an Elixir tuple, instead of
+                  the default Elixir list
+                </para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>
+                <literal>mkMap attrset</literal>
+              </term>
+              <listitem>
+                <para>
+                  Outputs the given attribute set as an Elixir map,
+                  instead of the default Elixir keyword list
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        </listitem>
+      </varlistentry>
     </variablelist>
     <para xml:id="pkgs-formats-result">
       These functions all return an attribute set with these values:
@@ -154,6 +245,19 @@
       </varlistentry>
       <varlistentry>
         <term>
+          <literal>lib</literal>
+        </term>
+        <listitem>
+          <para>
+            Utility functions for convenience, or special interactions
+            with the format. This attribute is optional. It may contain
+            inside a <literal>types</literal> attribute containing types
+            specific to this format.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <literal>generate</literal>
           <emphasis><literal>filename jsonValue</literal></emphasis>
         </term>
diff --git a/nixos/doc/manual/from_md/development/unit-handling.section.xml b/nixos/doc/manual/from_md/development/unit-handling.section.xml
index a6a654042f6f..57c4754c0018 100644
--- a/nixos/doc/manual/from_md/development/unit-handling.section.xml
+++ b/nixos/doc/manual/from_md/development/unit-handling.section.xml
@@ -88,9 +88,10 @@
         </listitem>
         <listitem>
           <para>
-            The rest of the behavior is decided whether the unit has
+            Further behavior depends on the unit having
             <literal>X-StopIfChanged</literal> in the
-            <literal>[Service]</literal> section set (exposed via
+            <literal>[Service]</literal> section set to
+            <literal>true</literal> (exposed via
             <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.stopIfChanged</link>).
             This is set to <literal>true</literal> by default and must
             be explicitly turned off if not wanted. If the flag is
@@ -100,17 +101,22 @@
             is <emphasis role="strong">restart</emphasis>ed. The goal of
             the flag is to make sure that the new unit never runs in the
             old environment which is still in place before the
-            activation script is run.
+            activation script is run. This behavior is different when
+            the service is socket-activated, as outlined in the
+            following steps.
           </para>
         </listitem>
         <listitem>
           <para>
             The last thing that is taken into account is whether the
-            unit is a service and socket-activated. Due to a bug, this
-            is currently only done when
-            <literal>X-StopIfChanged</literal> is set. If the unit is
-            socket-activated, the socket is stopped and started, and the
-            service is stopped and to be started by socket activation.
+            unit is a service and socket-activated. If
+            <literal>X-StopIfChanged</literal> is
+            <emphasis role="strong">not</emphasis> set, the service is
+            <emphasis role="strong">restart</emphasis>ed with the
+            others. If it is set, both the service and the socket are
+            <emphasis role="strong">stop</emphasis>ped and the socket is
+            <emphasis role="strong">start</emphasis>ed, leaving socket
+            activation to start the service when it’s needed.
           </para>
         </listitem>
       </itemizedlist>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 1ad1a8e3a36d..dc7279f9d5c8 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -50,6 +50,18 @@
           granular distinction between reloads and restarts.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://kops.sigs.k8s.io"><literal>kops</literal></link>
+          defaults to 1.22.4, which will enable
+          <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html">Instance
+          Metadata Service Version 2</link> and require tokens on new
+          clusters with Kubernetes 1.22. This will increase security by
+          default, but may break some types of workloads. See the
+          <link xlink:href="https://kops.sigs.k8s.io/releases/1.22-notes/">release
+          notes</link> for details.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-22.05-new-services">
@@ -110,6 +122,13 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://snowflake.torproject.org/">snowflake-proxy</link>,
+          a system to defeat internet censorship. Available as
+          <link xlink:href="options.html#opt-services.snowflake-proxy.enable">services.snowflake-proxy</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://ergo.chat">ergochat</link>, a modern
           IRC with IRCv3 features. Available as
           <link xlink:href="options.html#opt-services.ergochat.enable">services.ergochat</link>.
@@ -124,6 +143,13 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/postgres/pgadmin4">pgadmin4</link>,
+          an admin interface for the PostgreSQL database. Available at
+          <link xlink:href="options.html#opt-services.pgadmin.enable">services.pgadmin</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://github.com/sezanzeb/input-remapper">input-remapper</link>,
           an easy to use tool to change the mapping of your input device
           buttons. Available at
@@ -364,6 +390,116 @@
       </listitem>
       <listitem>
         <para>
+          The <literal>matrix-synapse</literal> service
+          (<literal>services.matrix-synapse</literal>) has been
+          converted to use the <literal>settings</literal> option
+          defined in RFC42. This means that options that are part of
+          your <literal>homeserver.yaml</literal> configuration, and
+          that were specified at the top-level of the module
+          (<literal>services.matrix-synapse</literal>) now need to be
+          moved into
+          <literal>services.matrix-synapse.settings</literal>. And while
+          not all options you may use are defined in there, they are
+          still supported, because you can set arbitrary values in this
+          freeform type.
+        </para>
+        <para>
+          An example to make the required migration clearer:
+        </para>
+        <para>
+          Before:
+        </para>
+        <programlisting language="bash">
+{
+  services.matrix-synapse = {
+    enable = true;
+
+    server_name = &quot;example.com&quot;;
+    public_baseurl = &quot;https://example.com:8448&quot;;
+
+    enable_registration = false;
+    registration_shared_secret = &quot;xohshaeyui8jic7uutuDogahkee3aehuaf6ei3Xouz4iicie5thie6nohNahceut&quot;;
+    macaroon_secret_key = &quot;xoo8eder9seivukaiPh1cheikohquuw8Yooreid0The4aifahth3Ou0aiShaiz4l&quot;;
+
+    tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
+    tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
+
+    listeners = [ {
+      port = 8448;
+      bind_address = &quot;&quot;;
+      type = &quot;http&quot;;
+      tls = true;
+      resources = [ {
+        names = [ &quot;client&quot; ];
+        compress = true;
+      } {
+        names = [ &quot;federation&quot; ];
+        compress = false;
+      } ];
+    } ];
+
+  };
+}
+</programlisting>
+        <para>
+          After:
+        </para>
+        <programlisting language="bash">
+{
+  services.matrix-synapse = {
+    enable = true;
+
+    # this attribute set holds all values that go into your homeserver.yaml configuration
+    # See https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml for
+    # possible values.
+    settings = {
+      server_name = &quot;example.com&quot;;
+      public_baseurl = &quot;https://example.com:8448&quot;;
+
+      enable_registration = false;
+      # pass `registration_shared_secret` and `macaroon_secret_key` via `extraConfigFiles` instead
+
+      tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
+      tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
+
+      listeners = [ {
+        port = 8448;
+        bind_address = [
+          &quot;::&quot;
+          &quot;0.0.0.0&quot;
+        ];
+        type = &quot;http&quot;;
+        tls = true;
+        resources = [ {
+          names = [ &quot;client&quot; ];
+          compress = true;
+        } {
+          names = [ &quot;federation&quot; ];
+          compress = false;
+        } ];
+      } ];
+    };
+
+    extraConfigFiles = [
+      /run/keys/matrix-synapse/secrets.yaml
+    ];
+  };
+}
+</programlisting>
+        <para>
+          The secrets in your original config should be migrated into a
+          YAML file that is included via
+          <literal>extraConfigFiles</literal>.
+        </para>
+        <para>
+          Additionally a few option defaults have been synced up with
+          upstream default values, for example the
+          <literal>max_upload_size</literal> grew from
+          <literal>10M</literal> to <literal>50M</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The MoinMoin wiki engine
           (<literal>services.moinmoin</literal>) has been removed,
           because Python 2 is being retired from nixpkgs.
@@ -548,6 +684,21 @@
       </listitem>
       <listitem>
         <para>
+          The F-PROT antivirus (<literal>fprot</literal> package) and
+          its service module were removed because it reached
+          <link xlink:href="https://kb.cyren.com/av-support/index.php?/Knowledgebase/Article/View/434/0/end-of-sale--end-of-life-for-f-prot-and-csam">end-of-life</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>bird1</literal> and its modules
+          <literal>services.bird</literal> as well as
+          <literal>services.bird6</literal> have been removed. Upgrade
+          to <literal>services.bird2</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The options
           <literal>networking.interfaces.&lt;name&gt;.ipv4.routes</literal>
           and
@@ -617,6 +768,13 @@
       </listitem>
       <listitem>
         <para>
+          <literal>pkgs.pgadmin</literal> now refers to
+          <literal>pkgs.pgadmin4</literal>. If you still need pgadmin3,
+          use <literal>pkgs.pgadmin3</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <literal>pkgs.noto-fonts-cjk</literal> is now deprecated in
           favor of <literal>pkgs.noto-fonts-cjk-sans</literal> and
           <literal>pkgs.noto-fonts-cjk-serif</literal> because they each
@@ -738,6 +896,70 @@
           <literal>false</literal>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.makeDesktopItem</literal> has been refactored to
+          provide a more idiomatic API. Specifically:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              All valid options as of FDO Desktop Entry specification
+              version 1.4 can now be passed in as explicit arguments
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>exec</literal> can now be null, for entries that
+              are not of type Application
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>mimeType</literal> argument is renamed to
+              <literal>mimeTypes</literal> for consistency
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>mimeTypes</literal>,
+              <literal>categories</literal>,
+              <literal>implements</literal>,
+              <literal>keywords</literal>, <literal>onlyShowIn</literal>
+              and <literal>notShowIn</literal> take lists of strings
+              instead of one string with semicolon separators
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>extraDesktopEntries</literal> renamed to
+              <literal>extraConfig</literal> for consistency
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Actions should now be provided as an attrset
+              <literal>actions</literal>, the <literal>Actions</literal>
+              line will be autogenerated.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>extraEntries</literal> is removed.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Additional validation is added both at eval time and at
+              build time.
+            </para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          See the <literal>vscode</literal> package for a more detailed
+          example.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-22.05-notable-changes">
@@ -947,6 +1169,16 @@
       </listitem>
       <listitem>
         <para>
+          The <literal>element-desktop</literal> package now has an
+          <literal>useKeytar</literal> option (defaults to
+          <literal>true</literal>), which allows disabling
+          <literal>keytar</literal> and in turn
+          <literal>libsecret</literal> usage (which binds to native
+          credential managers / keychain libraries).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The option <literal>services.thelounge.plugins</literal> has
           been added to allow installing plugins for The Lounge. Plugins
           can be found in
@@ -1069,6 +1301,14 @@
           <literal>tmux</literal>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          The polkit service, available at
+          <literal>security.polkit.enable</literal>, is now disabled by
+          default. It will automatically be enabled through services and
+          desktop environments as needed.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index bf66e66da626..fd812c540b92 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -19,6 +19,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - systemd services can now set [systemd.services.\<name\>.reloadTriggers](#opt-systemd.services) instead of `reloadIfChanged` for a more granular distinction between reloads and restarts.
 
+- [`kops`](https://kops.sigs.k8s.io) defaults to 1.22.4, which will enable [Instance Metadata Service Version 2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) and require tokens on new clusters with Kubernetes 1.22. This will increase security by default, but may break some types of workloads. See the [release notes](https://kops.sigs.k8s.io/releases/1.22-notes/) for details.
+
 ## New Services {#sec-release-22.05-new-services}
 
 - [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable).
@@ -35,10 +37,14 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [heisenbridge](https://github.com/hifi/heisenbridge), a bouncer-style Matrix IRC bridge. Available as [services.heisenbridge](options.html#opt-services.heisenbridge.enable).
 
+- [snowflake-proxy](https://snowflake.torproject.org/), a system to defeat internet censorship. Available as [services.snowflake-proxy](options.html#opt-services.snowflake-proxy.enable).
+
 - [ergochat](https://ergo.chat), a modern IRC with IRCv3 features. Available as [services.ergochat](options.html#opt-services.ergochat.enable).
 
 - [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable).
 
+- [pgadmin4](https://github.com/postgres/pgadmin4), an admin interface for the PostgreSQL database. Available at [services.pgadmin](options.html#opt-services.pgadmin.enable).
+
 - [input-remapper](https://github.com/sezanzeb/input-remapper), an easy to use tool to change the mapping of your input device buttons. Available at [services.input-remapper](options.html#opt-services.input-remapper.enable).
 
 - [InvoicePlane](https://invoiceplane.com), web application for managing and creating invoices. Available at [services.invoiceplane](options.html#opt-services.invoiceplane.enable).
@@ -122,6 +128,95 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `mailpile` email webclient (`services.mailpile`) has been removed due to its reliance on python2.
 
+- The `matrix-synapse` service (`services.matrix-synapse`) has been converted to use the `settings` option defined in RFC42.
+  This means that options that are part of your `homeserver.yaml` configuration, and that were specified at the top-level of the
+  module (`services.matrix-synapse`) now need to be moved into `services.matrix-synapse.settings`. And while not all options you
+  may use are defined in there, they are still supported, because you can set arbitrary values in this freeform type.
+
+  An example to make the required migration clearer:
+
+  Before:
+  ```nix
+  {
+    services.matrix-synapse = {
+      enable = true;
+
+      server_name = "example.com";
+      public_baseurl = "https://example.com:8448";
+
+      enable_registration = false;
+      registration_shared_secret = "xohshaeyui8jic7uutuDogahkee3aehuaf6ei3Xouz4iicie5thie6nohNahceut";
+      macaroon_secret_key = "xoo8eder9seivukaiPh1cheikohquuw8Yooreid0The4aifahth3Ou0aiShaiz4l";
+
+      tls_certificate_path = "/var/lib/acme/example.com/fullchain.pem";
+      tls_certificate_path = "/var/lib/acme/example.com/fullchain.pem";
+
+      listeners = [ {
+        port = 8448;
+        bind_address = "";
+        type = "http";
+        tls = true;
+        resources = [ {
+          names = [ "client" ];
+          compress = true;
+        } {
+          names = [ "federation" ];
+          compress = false;
+        } ];
+      } ];
+
+    };
+  }
+  ```
+
+  After:
+  ```nix
+  {
+    services.matrix-synapse = {
+      enable = true;
+
+      # this attribute set holds all values that go into your homeserver.yaml configuration
+      # See https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml for
+      # possible values.
+      settings = {
+        server_name = "example.com";
+        public_baseurl = "https://example.com:8448";
+
+        enable_registration = false;
+        # pass `registration_shared_secret` and `macaroon_secret_key` via `extraConfigFiles` instead
+
+        tls_certificate_path = "/var/lib/acme/example.com/fullchain.pem";
+        tls_certificate_path = "/var/lib/acme/example.com/fullchain.pem";
+
+        listeners = [ {
+          port = 8448;
+          bind_address = [
+            "::"
+            "0.0.0.0"
+          ];
+          type = "http";
+          tls = true;
+          resources = [ {
+            names = [ "client" ];
+            compress = true;
+          } {
+            names = [ "federation" ];
+            compress = false;
+          } ];
+        } ];
+      };
+
+      extraConfigFiles = [
+        /run/keys/matrix-synapse/secrets.yaml
+      ];
+    };
+  }
+  ```
+
+  The secrets in your original config should be migrated into a YAML file that is included via `extraConfigFiles`.
+
+  Additionally a few option defaults have been synced up with upstream default values, for example the `max_upload_size` grew from `10M` to `50M`.
+
 - The MoinMoin wiki engine (`services.moinmoin`) has been removed, because Python 2 is being retired from nixpkgs.
 
 - The `wafHook` hook now honors `NIX_BUILD_CORES` when `enableParallelBuilding` is not set explicitly. Packages can restore the old behaviour by setting `enableParallelBuilding=false`.
@@ -178,6 +273,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `tilp2` was removed together with its module
 
+- The F-PROT antivirus (`fprot` package) and its service module were removed because it
+  reached [end-of-life](https://kb.cyren.com/av-support/index.php?/Knowledgebase/Article/View/434/0/end-of-sale--end-of-life-for-f-prot-and-csam).
+
+- `bird1` and its modules `services.bird` as well as `services.bird6` have been removed. Upgrade to `services.bird2`.
+
 - The options `networking.interfaces.<name>.ipv4.routes` and `networking.interfaces.<name>.ipv6.routes` are no longer ignored when using networkd instead of the default scripted network backend by setting `networking.useNetworkd` to `true`.
 
 - MultiMC has been replaced with the fork PolyMC due to upstream developers being hostile to 3rd party package maintainers. PolyMC removes all MultiMC branding and is aimed at providing proper 3rd party packages like the one contained in Nixpkgs. This change affects the data folder where game instances and other save and configuration files are stored. Users with existing installations should rename `~/.local/share/multimc` to `~/.local/share/polymc`. The main config file's path has also moved from `~/.local/share/multimc/multimc.cfg` to `~/.local/share/polymc/polymc.cfg`.
@@ -196,6 +296,8 @@ In addition to numerous new and upgraded packages, this release has the followin
   you should change the package you refer to. If you don't need them update your
   commands from `otelcontribcol` to `otelcorecol` and enjoy a 7x smaller binary.
 
+- `pkgs.pgadmin` now refers to `pkgs.pgadmin4`.
+  If you still need pgadmin3, use `pkgs.pgadmin3`.
 
 - `pkgs.noto-fonts-cjk` is now deprecated in favor of `pkgs.noto-fonts-cjk-sans`
   and `pkgs.noto-fonts-cjk-serif` because they each have different release
@@ -229,6 +331,18 @@ In addition to numerous new and upgraded packages, this release has the followin
   pipewire-media-session is deprecated by upstream and not recommended, but can still be manually enabled by setting
   `services.pipewire.media-session.enable` to `true` and `services.pipewire.wireplumber.enable` to `false`.
 
+- `pkgs.makeDesktopItem` has been refactored to provide a more idiomatic API. Specifically:
+  - All valid options as of FDO Desktop Entry specification version 1.4 can now be passed in as explicit arguments
+  - `exec` can now be null, for entries that are not of type Application
+  - `mimeType` argument is renamed to `mimeTypes` for consistency
+  - `mimeTypes`, `categories`, `implements`, `keywords`, `onlyShowIn` and `notShowIn` take lists of strings instead of one string with semicolon separators
+  - `extraDesktopEntries` renamed to `extraConfig` for consistency
+  - Actions should now be provided as an attrset `actions`, the `Actions` line will be autogenerated.
+  - `extraEntries` is removed.
+  - Additional validation is added both at eval time and at build time.
+
+  See the `vscode` package for a more detailed example.
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 ## Other Notable Changes {#sec-release-22.05-notable-changes}
@@ -315,6 +429,10 @@ In addition to numerous new and upgraded packages, this release has the followin
   using `fetchgit` or `fetchhg` if the argument `fetchSubmodules`
   is set to `true`.
 
+- The `element-desktop` package now has an `useKeytar` option (defaults to `true`),
+  which allows disabling `keytar` and in turn `libsecret` usage
+  (which binds to native credential managers / keychain libraries).
+
 - The option `services.thelounge.plugins` has been added to allow installing plugins for The Lounge. Plugins can be found in `pkgs.theLoungePlugins.plugins` and `pkgs.theLoungePlugins.themes`.
 
 - The `firmwareLinuxNonfree` package has been renamed to `linux-firmware`.
@@ -353,4 +471,6 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `programs.tmux` has a new option `plugins` that accepts a list of packages from the `tmuxPlugins` group. The specified packages are added to the system and loaded by `tmux`.
 
+- The polkit service, available at `security.polkit.enable`, is now disabled by default. It will automatically be enabled through services and desktop environments as needed.
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 190c4db4d49d..733f9ca522be 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -150,7 +150,8 @@ rec {
         rm '${output}'
       fi
 
-      inherit_errexit_restore=$(shopt -p inherit_errexit)
+      inherit_errexit_enabled=0
+      shopt -pq inherit_errexit && inherit_errexit_enabled=1
       shopt -s inherit_errexit
     ''
     + concatStringsSep
@@ -170,7 +171,7 @@ rec {
       ' <<'EOF'
       ${builtins.toJSON set}
       EOF
-      $inherit_errexit_restore
+      (( ! $inherit_errexit_enabled )) && shopt -u inherit_errexit
     '';
 
   systemdUtils = {
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 99bdb11b0112..5b60b17312f9 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -83,6 +83,7 @@ in {
         b43Firmware_5_1_138
         b43Firmware_6_30_163_46
         b43FirmwareCutter
+        xow_dongle-firmware
       ] ++ optional pkgs.stdenv.hostPlatform.isx86 facetimehd-firmware;
     })
     (mkIf cfg.wirelessRegulatoryDatabase {
diff --git a/nixos/modules/hardware/xone.nix b/nixos/modules/hardware/xone.nix
new file mode 100644
index 000000000000..89690d8c6fb1
--- /dev/null
+++ b/nixos/modules/hardware/xone.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.hardware.xone;
+in
+{
+  options.hardware.xone = {
+    enable = mkEnableOption "the xone driver for Xbox One and Xbobx Series X|S accessories";
+  };
+
+  config = mkIf cfg.enable {
+    boot = {
+      blacklistedKernelModules = [ "xpad" "mt76x2u" ];
+      extraModulePackages = with config.boot.kernelPackages; [ xone ];
+    };
+    hardware.firmware = [ pkgs.xow_dongle-firmware ];
+  };
+
+  meta = {
+    maintainers = with maintainers; [ rhysmdnz ];
+  };
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64.nix b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
index 165e2aac27b4..321793882f4c 100644
--- a/nixos/modules/installer/sd-card/sd-image-aarch64.nix
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
@@ -24,6 +24,9 @@
         [pi3]
         kernel=u-boot-rpi3.bin
 
+        [pi02]
+        kernel=u-boot-rpi3.bin
+
         [pi4]
         kernel=u-boot-rpi4.bin
         enable_gic=1
@@ -33,6 +36,9 @@
         # what the pi3 firmware does by default.
         disable_overscan=1
 
+        # Supported in newer board revisions
+        arm_boost=1
+
         [all]
         # Boot in 64-bit mode.
         arm_64bit=1
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index b7746ddc2115..9304c307af2f 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -129,7 +129,7 @@ let
       genericName = "View NixOS documentation in a web browser";
       icon = "nix-snowflake";
       exec = "nixos-help";
-      categories = "System";
+      categories = ["System"];
     };
 
     in pkgs.symlinkJoin {
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 1b4105c676d9..7d1faa50f4bf 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -89,7 +89,7 @@ in
       prayer = 49;
       mpd = 50;
       clamav = 51;
-      fprot = 52;
+      #fprot = 52; # unused
       # bind = 53; #dynamically allocated as of 2021-09-03
       wwwrun = 54;
       #adm = 55; # unused
@@ -412,7 +412,7 @@ in
       prayer = 49;
       mpd = 50;
       clamav = 51;
-      fprot = 52;
+      #fprot = 52; # unused
       #bind = 53; # unused
       wwwrun = 54;
       adm = 55;
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 6c526f6d4f2d..6c072021ed83 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -1,12 +1,17 @@
 { config, lib, options, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.system.nixos;
   opt = options.system.nixos;
-in
 
+  inherit (lib)
+    concatStringsSep mapAttrsToList toLower
+    literalExpression mkRenamedOptionModule mkDefault mkOption trivial types;
+
+  attrsToText = attrs:
+    concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}="${toString v}"'') attrs);
+
+in
 {
   imports = [
     (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
@@ -101,22 +106,30 @@ in
     # Generate /etc/os-release.  See
     # https://www.freedesktop.org/software/systemd/man/os-release.html for the
     # format.
-    environment.etc.os-release.text =
-      ''
-        NAME=NixOS
-        ID=nixos
-        VERSION="${cfg.release} (${cfg.codeName})"
-        VERSION_CODENAME=${toLower cfg.codeName}
-        VERSION_ID="${cfg.release}"
-        BUILD_ID="${cfg.version}"
-        PRETTY_NAME="NixOS ${cfg.release} (${cfg.codeName})"
-        LOGO="nix-snowflake"
-        HOME_URL="https://nixos.org/"
-        DOCUMENTATION_URL="https://nixos.org/learn.html"
-        SUPPORT_URL="https://nixos.org/community.html"
-        BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
-      '';
-
+    environment.etc = {
+      "lsb-release".text = attrsToText {
+        LSB_VERSION = "${cfg.release} (${cfg.codeName})";
+        DISTRIB_ID = "nixos";
+        DISTRIB_RELEASE = cfg.release;
+        DISTRIB_CODENAME = toLower cfg.codeName;
+        DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
+      };
+
+      "os-release".text = attrsToText {
+        NAME = "NixOS";
+        ID = "nixos";
+        VERSION = "${cfg.release} (${cfg.codeName})";
+        VERSION_CODENAME = toLower cfg.codeName;
+        VERSION_ID = cfg.release;
+        BUILD_ID = cfg.version;
+        PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+        LOGO = "nix-snowflake";
+        HOME_URL = "https://nixos.org/";
+        DOCUMENTATION_URL = "https://nixos.org/learn.html";
+        SUPPORT_URL = "https://nixos.org/community.html";
+        BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+      };
+    };
   };
 
   # uses version info nixpkgs, which requires a full nixpkgs path
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2d9942a85b36..6430993d5c63 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -91,6 +91,7 @@
   ./hardware/video/switcheroo-control.nix
   ./hardware/video/uvcvideo/default.nix
   ./hardware/video/webcam/facetimehd.nix
+  ./hardware/xone.nix
   ./hardware/xpadneo.nix
   ./i18n/input-method/default.nix
   ./i18n/input-method/fcitx.nix
@@ -168,6 +169,7 @@
   ./programs/iotop.nix
   ./programs/java.nix
   ./programs/k40-whisperer.nix
+  ./programs/kclock.nix
   ./programs/kdeconnect.nix
   ./programs/kbdlight.nix
   ./programs/less.nix
@@ -254,6 +256,7 @@
   ./security/tpm2.nix
   ./services/admin/meshcentral.nix
   ./services/admin/oxidized.nix
+  ./services/admin/pgadmin.nix
   ./services/admin/salt/master.nix
   ./services/admin/salt/minion.nix
   ./services/amqp/activemq/default.nix
@@ -395,6 +398,7 @@
   ./services/development/jupyterhub/default.nix
   ./services/development/rstudio-server/default.nix
   ./services/development/lorri.nix
+  ./services/development/zammad.nix
   ./services/display-managers/greetd.nix
   ./services/editors/emacs.nix
   ./services/editors/infinoted.nix
@@ -452,6 +456,7 @@
   ./services/hardware/vdr.nix
   ./services/hardware/xow.nix
   ./services/home-automation/home-assistant.nix
+  ./services/home-automation/zigbee2mqtt.nix
   ./services/logging/SystemdJournal2Gelf.nix
   ./services/logging/awstats.nix
   ./services/logging/filebeat.nix
@@ -496,6 +501,7 @@
   ./services/mail/roundcube.nix
   ./services/mail/sympa.nix
   ./services/mail/nullmailer.nix
+  ./services/matrix/matrix-synapse.nix
   ./services/matrix/mjolnir.nix
   ./services/matrix/pantalaimon.nix
   ./services/misc/ananicy.nix
@@ -562,7 +568,6 @@
   ./services/misc/matrix-appservice-discord.nix
   ./services/misc/matrix-appservice-irc.nix
   ./services/misc/matrix-conduit.nix
-  ./services/misc/matrix-synapse.nix
   ./services/misc/mautrix-facebook.nix
   ./services/misc/mautrix-telegram.nix
   ./services/misc/mbpfan.nix
@@ -623,7 +628,6 @@
   ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
   ./services/misc/xmrig.nix
-  ./services/misc/zigbee2mqtt.nix
   ./services/misc/zoneminder.nix
   ./services/misc/zookeeper.nix
   ./services/monitoring/alerta.nix
@@ -874,6 +878,7 @@
   ./services/networking/shorewall6.nix
   ./services/networking/shout.nix
   ./services/networking/sniproxy.nix
+  ./services/networking/snowflake-proxy.nix
   ./services/networking/smartdns.nix
   ./services/networking/smokeping.nix
   ./services/networking/softether.nix
@@ -948,7 +953,6 @@
   ./services/security/clamav.nix
   ./services/security/fail2ban.nix
   ./services/security/fprintd.nix
-  ./services/security/fprot.nix
   ./services/security/haka.nix
   ./services/security/haveged.nix
   ./services/security/hockeypuck.nix
diff --git a/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix
index 8c10d7c4df39..76b42168c198 100644
--- a/nixos/modules/programs/firejail.nix
+++ b/nixos/modules/programs/firejail.nix
@@ -17,8 +17,8 @@ let
         then value
         else { executable = value; profile = null; extraArgs = []; };
         args = lib.escapeShellArgs (
-          (optional (opts.profile != null) "--profile=${toString opts.profile}")
-          ++ opts.extraArgs
+          opts.extraArgs
+          ++ (optional (opts.profile != null) "--profile=${toString opts.profile}")
           );
       in
       ''
diff --git a/nixos/modules/programs/kclock.nix b/nixos/modules/programs/kclock.nix
new file mode 100644
index 000000000000..42d81d2798ba
--- /dev/null
+++ b/nixos/modules/programs/kclock.nix
@@ -0,0 +1,13 @@
+{ lib, pkgs, config, ... }:
+with lib;
+let
+  cfg = config.programs.kclock;
+  kclockPkg = pkgs.libsForQt5.kclock;
+in {
+  options.programs.kclock = { enable = mkEnableOption "Enable KClock"; };
+
+  config = mkIf cfg.enable {
+    services.dbus.packages = [ kclockPkg ];
+    environment.systemPackages = [ kclockPkg ];
+  };
+}
diff --git a/nixos/modules/programs/phosh.nix b/nixos/modules/programs/phosh.nix
index cba3f73768ec..ad875616ac9e 100644
--- a/nixos/modules/programs/phosh.nix
+++ b/nixos/modules/programs/phosh.nix
@@ -8,18 +8,17 @@ let
   # Based on https://source.puri.sm/Librem5/librem5-base/-/blob/4596c1056dd75ac7f043aede07887990fd46f572/default/sm.puri.OSK0.desktop
   oskItem = pkgs.makeDesktopItem {
     name = "sm.puri.OSK0";
-    type = "Application";
     desktopName = "On-screen keyboard";
     exec = "${pkgs.squeekboard}/bin/squeekboard";
-    categories = "GNOME;Core;";
-    extraEntries = ''
-      OnlyShowIn=GNOME;
-      NoDisplay=true
-      X-GNOME-Autostart-Phase=Panel
-      X-GNOME-Provides=inputmethod
-      X-GNOME-Autostart-Notify=true
-      X-GNOME-AutoRestart=true
-    '';
+    categories = [ "GNOME" "Core" ];
+    onlyShowIn = [ "GNOME" ];
+    noDisplay = true;
+    extraConfig = {
+      X-GNOME-Autostart-Phase = "Panel";
+      X-GNOME-Provides = "inputmethod";
+      X-GNOME-Autostart-Notify = "true";
+      X-GNOME-AutoRestart = "true";
+    };
   };
 
   phocConfigType = types.submodule {
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index bb9904d19560..01b047281344 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -134,6 +134,7 @@ in {
         '';
       };
     };
+    security.polkit.enable = true;
     security.pam.services.swaylock = {};
     hardware.opengl.enable = mkDefault true;
     fonts.enableDefaultFonts = mkDefault true;
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index d72ff1c6f170..195cf87e6a85 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -50,6 +50,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "flashpolicyd" ] "The flashpolicyd module has been removed. Adobe Flash Player is deprecated.")
     (mkRemovedOptionModule [ "services" "fourStore" ] "The fourStore module has been removed")
     (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed")
+    (mkRemovedOptionModule [ "services" "fprot" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "frab" ] "The frab module has been removed")
     (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "mailpile" ] "The corresponding package was removed from nixpkgs.")
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 9f295db84fd6..c0ef8b5f30bd 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -518,7 +518,7 @@ let
                 auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
               '' +
               optionalString cfg.googleAuthenticator.enable ''
-                auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
+                auth required ${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
               '' +
               optionalString cfg.duoSecurity.enable ''
                 auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index d9c58152f1fa..1ba149745c65 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -12,11 +12,7 @@ in
 
   options = {
 
-    security.polkit.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Whether to enable PolKit.";
-    };
+    security.polkit.enable = mkEnableOption "polkit";
 
     security.polkit.extraConfig = mkOption {
       type = types.lines;
diff --git a/nixos/modules/security/systemd-confinement.nix b/nixos/modules/security/systemd-confinement.nix
index 0e3ec5af323e..f3a2de3bf87a 100644
--- a/nixos/modules/security/systemd-confinement.nix
+++ b/nixos/modules/security/systemd-confinement.nix
@@ -175,8 +175,8 @@ in {
       serviceName = "${name}.service";
       excludedPath = rootPaths;
     } ''
-      mkdir -p "$out/lib/systemd/system"
-      serviceFile="$out/lib/systemd/system/$serviceName"
+      mkdir -p "$out/lib/systemd/system/$serviceName.d"
+      serviceFile="$out/lib/systemd/system/$serviceName.d/confinement.conf"
 
       echo '[Service]' > "$serviceFile"
 
diff --git a/nixos/modules/services/admin/pgadmin.nix b/nixos/modules/services/admin/pgadmin.nix
new file mode 100644
index 000000000000..80b681454104
--- /dev/null
+++ b/nixos/modules/services/admin/pgadmin.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  pkg = pkgs.pgadmin4;
+  cfg = config.services.pgadmin;
+
+  _base = with types; [ int bool str ];
+  base = with types; oneOf ([ (listOf (oneOf _base)) (attrsOf (oneOf _base)) ] ++ _base);
+
+  formatAttrset = attr:
+    "{${concatStringsSep "\n" (mapAttrsToList (key: value: "${builtins.toJSON key}: ${formatPyValue value},") attr)}}";
+
+  formatPyValue = value:
+    if builtins.isString value then builtins.toJSON value
+    else if value ? _expr then value._expr
+    else if builtins.isInt value then toString value
+    else if builtins.isBool value then (if value then "True" else "False")
+    else if builtins.isAttrs value then (formatAttrset value)
+    else if builtins.isList value then "[${concatStringsSep "\n" (map (v: "${formatPyValue v},") value)}]"
+    else throw "Unrecognized type";
+
+  formatPy = attrs:
+    concatStringsSep "\n" (mapAttrsToList (key: value: "${key} = ${formatPyValue value}") attrs);
+
+  pyType = with types; attrsOf (oneOf [ (attrsOf base) (listOf base) base ]);
+in
+{
+  options.services.pgadmin = {
+    enable = mkEnableOption "PostgreSQL Admin 4";
+
+    port = mkOption {
+      description = "Port for pgadmin4 to run on";
+      type = types.port;
+      default = 5050;
+    };
+
+    initialEmail = mkOption {
+      description = "Initial email for the pgAdmin account.";
+      type = types.str;
+    };
+
+    initialPasswordFile = mkOption {
+      description = ''
+        Initial password file for the pgAdmin account.
+        NOTE: Should be string not a store path, to prevent the password from being world readable.
+      '';
+      type = types.path;
+    };
+
+    openFirewall = mkEnableOption "firewall passthrough for pgadmin4";
+
+    settings = mkOption {
+      description = ''
+        Settings for pgadmin4.
+        <link xlink:href="https://www.pgadmin.org/docs/pgadmin4/development/config_py.html">Documentation</link>.
+      '';
+      type = pyType;
+      default= {};
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ];
+
+    services.pgadmin.settings = {
+      DEFAULT_SERVER_PORT = cfg.port;
+      SERVER_MODE = true;
+    } // (optionalAttrs cfg.openFirewall {
+      DEFAULT_SERVER = mkDefault "::";
+    });
+
+    systemd.services.pgadmin = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      requires = [ "network.target" ];
+      # we're adding this optionally so just in case there's any race it'll be caught
+      # in case postgres doesn't start, pgadmin will just start normally
+      wants = [ "postgresql.service" ];
+
+      path = [ config.services.postgresql.package pkgs.coreutils pkgs.bash ];
+
+      preStart = ''
+        # NOTE: this is idempotent (aka running it twice has no effect)
+        (
+          # Email address:
+          echo ${escapeShellArg cfg.initialEmail}
+
+          # file might not contain newline. echo hack fixes that.
+          PW=$(cat ${escapeShellArg cfg.initialPasswordFile})
+
+          # Password:
+          echo "$PW"
+          # Retype password:
+          echo "$PW"
+        ) | ${pkg}/bin/pgadmin4-setup
+      '';
+
+      restartTriggers = [
+        "/etc/pgadmin/config_system.py"
+      ];
+
+      serviceConfig = {
+        User = "pgadmin";
+        DynamicUser = true;
+        LogsDirectory = "pgadmin";
+        StateDirectory = "pgadmin";
+        ExecStart = "${pkg}/bin/pgadmin4";
+      };
+    };
+
+    users.users.pgadmin = {
+      isSystemUser = true;
+      group = "pgadmin";
+    };
+
+    users.groups.pgadmin = {};
+
+    environment.etc."pgadmin/config_system.py" = {
+      text = formatPy cfg.settings;
+      mode = "0600";
+      user = "pgadmin";
+      group = "pgadmin";
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index b82aca3976f0..6d5ce98df895 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -44,24 +44,24 @@ let
 
   optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
     # global options
-    ++ [ "--stream.bind_to_address ${cfg.listenAddress}" ]
-    ++ [ "--stream.port ${toString cfg.port}" ]
-    ++ optionalNull cfg.sampleFormat "--stream.sampleformat ${cfg.sampleFormat}"
-    ++ optionalNull cfg.codec "--stream.codec ${cfg.codec}"
-    ++ optionalNull cfg.streamBuffer "--stream.stream_buffer ${toString cfg.streamBuffer}"
-    ++ optionalNull cfg.buffer "--stream.buffer ${toString cfg.buffer}"
+    ++ [ "--stream.bind_to_address=${cfg.listenAddress}" ]
+    ++ [ "--stream.port=${toString cfg.port}" ]
+    ++ optionalNull cfg.sampleFormat "--stream.sampleformat=${cfg.sampleFormat}"
+    ++ optionalNull cfg.codec "--stream.codec=${cfg.codec}"
+    ++ optionalNull cfg.streamBuffer "--stream.stream_buffer=${toString cfg.streamBuffer}"
+    ++ optionalNull cfg.buffer "--stream.buffer=${toString cfg.buffer}"
     ++ optional cfg.sendToMuted "--stream.send_to_muted"
     # tcp json rpc
-    ++ [ "--tcp.enabled ${toString cfg.tcp.enable}" ]
+    ++ [ "--tcp.enabled=${toString cfg.tcp.enable}" ]
     ++ optionals cfg.tcp.enable [
-      "--tcp.bind_to_address ${cfg.tcp.listenAddress}"
-      "--tcp.port ${toString cfg.tcp.port}" ]
+      "--tcp.bind_to_address=${cfg.tcp.listenAddress}"
+      "--tcp.port=${toString cfg.tcp.port}" ]
      # http json rpc
-    ++ [ "--http.enabled ${toString cfg.http.enable}" ]
+    ++ [ "--http.enabled=${toString cfg.http.enable}" ]
     ++ optionals cfg.http.enable [
-      "--http.bind_to_address ${cfg.http.listenAddress}"
-      "--http.port ${toString cfg.http.port}"
-    ] ++ optional (cfg.http.docRoot != null) "--http.doc_root \"${toString cfg.http.docRoot}\"");
+      "--http.bind_to_address=${cfg.http.listenAddress}"
+      "--http.port=${toString cfg.http.port}"
+    ] ++ optional (cfg.http.docRoot != null) "--http.doc_root=\"${toString cfg.http.docRoot}\"");
 
 in {
   imports = [
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 88bde4e91557..7d9198d20e8c 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -266,7 +266,7 @@ in
           in
           ''
             export KUBECONFIG=${clusterAdminKubeconfig}
-            ${kubectl}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
+            ${kubernetes}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
           '';
         })]);
 
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index 7da92cc9f264..5fecc64b4f70 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -30,6 +30,8 @@ in {
 
     environment.systemPackages = [ pkgs.flatpak ];
 
+    security.polkit.enable = true;
+
     services.dbus.packages = [ pkgs.flatpak ];
 
     systemd.packages = [ pkgs.flatpak ];
diff --git a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
index 284d8c394a61..9aa51b61431d 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
@@ -8,7 +8,7 @@
   },
   "context.modules": [
     {
-      "name": "libpipewire-module-rtkit",
+      "name": "libpipewire-module-rt",
       "args": {},
       "flags": [
         "ifexists",
diff --git a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
new file mode 100644
index 000000000000..c7f58fd5799a
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
@@ -0,0 +1,118 @@
+{
+  "context.properties": {
+    "link.max-buffers": 16,
+    "core.daemon": true,
+    "core.name": "pipewire-0",
+    "settings.check-quantum": true,
+    "settings.check-rate": true,
+    "vm.overrides": {
+      "default.clock.min-quantum": 1024
+    }
+  },
+  "context.spa-libs": {
+    "audio.convert.*": "audioconvert/libspa-audioconvert",
+    "api.alsa.*": "alsa/libspa-alsa",
+    "support.*": "support/libspa-support"
+  },
+  "context.modules": [
+    {
+      "name": "libpipewire-module-rt",
+      "args": {
+        "nice.level": -11
+      },
+      "flags": [
+        "ifexists",
+        "nofail"
+      ]
+    },
+    {
+      "name": "libpipewire-module-protocol-native"
+    },
+    {
+      "name": "libpipewire-module-profiler"
+    },
+    {
+      "name": "libpipewire-module-metadata"
+    },
+    {
+      "name": "libpipewire-module-spa-node-factory"
+    },
+    {
+      "name": "libpipewire-module-client-node"
+    },
+    {
+      "name": "libpipewire-module-access",
+      "args": {}
+    },
+    {
+      "name": "libpipewire-module-adapter"
+    },
+    {
+      "name": "libpipewire-module-link-factory"
+    }
+  ],
+  "context.objects": [
+    {
+      "factory": "metadata",
+      "args": {
+        "metadata.name": "default"
+      }
+    },
+    {
+      "factory": "spa-node-factory",
+      "args": {
+        "factory.name": "support.node.driver",
+        "node.name": "Dummy-Driver",
+        "node.group": "pipewire.dummy",
+        "priority.driver": 20000
+      }
+    },
+    {
+      "factory": "spa-node-factory",
+      "args": {
+        "factory.name": "support.node.driver",
+        "node.name": "Freewheel-Driver",
+        "priority.driver": 19000,
+        "node.group": "pipewire.freewheel",
+        "node.freewheel": true
+      }
+    },
+    {
+      "factory": "adapter",
+      "args": {
+        "factory.name": "api.alsa.pcm.source",
+        "node.name": "system",
+        "node.description": "system",
+        "media.class": "Audio/Source",
+        "api.alsa.path": "hw:0",
+        "node.suspend-on-idle": true,
+        "resample.disable": true,
+        "channelmix.disable": true,
+        "adapter.auto-port-config": {
+          "mode": "dsp",
+          "monitor": false,
+          "position": "unknown"
+        }
+      }
+    },
+    {
+      "factory": "adapter",
+      "args": {
+        "factory.name": "api.alsa.pcm.sink",
+        "node.name": "system",
+        "node.description": "system",
+        "media.class": "Audio/Sink",
+        "api.alsa.path": "hw:0",
+        "node.suspend-on-idle": true,
+        "resample.disable": true,
+        "channelmix.disable": true,
+        "adapter.auto-port-config": {
+          "mode": "dsp",
+          "monitor": false,
+          "position": "unknown"
+        }
+      }
+    }
+  ],
+  "context.exec": []
+}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index 3ed994f11145..df0f62556dff 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
@@ -6,8 +6,10 @@
   },
   "context.modules": [
     {
-      "name": "libpipewire-module-rtkit",
-      "args": {},
+      "name": "libpipewire-module-rt",
+      "args": {
+        "nice.level": -11
+      },
       "flags": [
         "ifexists",
         "nofail"
@@ -37,6 +39,61 @@
       }
     }
   ],
-  "context.exec": [],
-  "stream.properties": {}
+  "context.exec": [
+    {
+      "path": "pactl",
+      "args": "load-module module-always-sink"
+    }
+  ],
+  "stream.properties": {},
+  "pulse.rules": [
+    {
+      "matches": [
+        {}
+      ],
+      "actions": {
+        "update-props": {}
+      }
+    },
+    {
+      "matches": [
+        {
+          "application.process.binary": "teams"
+        },
+        {
+          "application.process.binary": "skypeforlinux"
+        }
+      ],
+      "actions": {
+        "quirks": [
+          "force-s16-info"
+        ]
+      }
+    },
+    {
+      "matches": [
+        {
+          "application.process.binary": "firefox"
+        }
+      ],
+      "actions": {
+        "quirks": [
+          "remove-capture-dont-move"
+        ]
+      }
+    },
+    {
+      "matches": [
+        {
+          "application.name": "~speech-dispatcher*"
+        }
+      ],
+      "actions": {
+        "update-props": {
+          "pulse.min.req": "1024/48000",
+          "pulse.min.quantum": "1024/48000"
+        }
+      }
+    }
+  ]
 }
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index a923ab4db235..7c79f0168c02 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
@@ -3,6 +3,7 @@
     "link.max-buffers": 16,
     "core.daemon": true,
     "core.name": "pipewire-0",
+    "default.clock.min-quantum": 16,
     "vm.overrides": {
       "default.clock.min-quantum": 1024
     }
@@ -19,8 +20,10 @@
   },
   "context.modules": [
     {
-      "name": "libpipewire-module-rtkit",
-      "args": {},
+      "name": "libpipewire-module-rt",
+      "args": {
+        "nice.level": -11
+      },
       "flags": [
         "ifexists",
         "nofail"
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index c3cfd46e61c2..59e9342a6ea1 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -25,15 +25,18 @@ let
     client = lib.importJSON ./daemon/client.conf.json;
     client-rt = lib.importJSON ./daemon/client-rt.conf.json;
     jack = lib.importJSON ./daemon/jack.conf.json;
+    minimal = lib.importJSON ./daemon/minimal.conf.json;
     pipewire = lib.importJSON ./daemon/pipewire.conf.json;
     pipewire-pulse = lib.importJSON ./daemon/pipewire-pulse.conf.json;
   };
 
+  useSessionManager = cfg.wireplumber.enable || cfg.media-session.enable;
+
   configs = {
     client = recursiveUpdate defaults.client cfg.config.client;
     client-rt = recursiveUpdate defaults.client-rt cfg.config.client-rt;
     jack = recursiveUpdate defaults.jack cfg.config.jack;
-    pipewire = recursiveUpdate defaults.pipewire cfg.config.pipewire;
+    pipewire = recursiveUpdate (if useSessionManager then defaults.pipewire else defaults.minimal) cfg.config.pipewire;
     pipewire-pulse = recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse;
   };
 in {
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
new file mode 100644
index 000000000000..d457a6071873
--- /dev/null
+++ b/nixos/modules/services/development/zammad.nix
@@ -0,0 +1,323 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zammad;
+  settingsFormat = pkgs.formats.yaml { };
+  filterNull = filterAttrs (_: v: v != null);
+  serviceConfig = {
+    Type = "simple";
+    Restart = "always";
+
+    User = "zammad";
+    Group = "zammad";
+    PrivateTmp = true;
+    StateDirectory = "zammad";
+    WorkingDirectory = cfg.dataDir;
+  };
+  environment = {
+    RAILS_ENV = "production";
+    NODE_ENV = "production";
+    RAILS_SERVE_STATIC_FILES = "true";
+    RAILS_LOG_TO_STDOUT = "true";
+  };
+  databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
+in
+{
+
+  options = {
+    services.zammad = {
+      enable = mkEnableOption "Zammad, a web-based, open source user support/ticketing solution.";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zammad;
+        defaultText = literalExpression "pkgs.zammad";
+        description = "Zammad package to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/zammad";
+        description = ''
+          Path to a folder that will contain Zammad working directory.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "192.168.23.42";
+        description = "Host address.";
+      };
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to open firewall ports for Zammad";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 3000;
+        description = "Web service port.";
+      };
+
+      websocketPort = mkOption {
+        type = types.port;
+        default = 6042;
+        description = "Websocket service port.";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "PostgreSQL" "MySQL" ];
+          default = "PostgreSQL";
+          example = "MySQL";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.nullOr types.str;
+          default = {
+            PostgreSQL = "/run/postgresql";
+            MySQL = "localhost";
+          }.${cfg.database.type};
+          defaultText = literalExpression ''
+            {
+              PostgreSQL = "/run/postgresql";
+              MySQL = "localhost";
+            }.''${config.services.zammad.database.type};
+          '';
+          description = ''
+            Database host address.
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.port;
+          default = null;
+          description = "Database port. Use <literal>null</literal> for default port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zammad";
+          description = ''
+            Database name.
+          '';
+        };
+
+        user = mkOption {
+          type = types.nullOr types.str;
+          default = "zammad";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zammad-dbpassword";
+          description = ''
+            A file containing the password for <option>services.zammad.database.user</option>.
+          '';
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+
+        settings = mkOption {
+          type = settingsFormat.type;
+          default = { };
+          example = literalExpression ''
+            {
+            }
+          '';
+          description = ''
+            The <filename>database.yml</filename> configuration file as key value set.
+            See <link xlink:href='TODO' />
+            for list of configuration parameters.
+          '';
+        };
+      };
+
+      secretKeyBaseFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/secret_key_base";
+        description = ''
+          The path to a file containing the
+          <literal>secret_key_base</literal> secret.
+
+          Zammad uses <literal>secret_key_base</literal> to encrypt
+          the cookie store, which contains session data, and to digest
+          user auth tokens.
+
+          Needs to be a 64 byte long string of hexadecimal
+          characters. You can generate one by running
+
+          <screen>
+          <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file
+          </screen>
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.zammad.database.settings = {
+      production = mapAttrs (_: v: mkDefault v) (filterNull {
+        adapter = {
+          PostgreSQL = "postgresql";
+          MySQL = "mysql2";
+        }.${cfg.database.type};
+        database = cfg.database.name;
+        pool = 50;
+        timeout = 5000;
+        encoding = "utf8";
+        username = cfg.database.user;
+        host = cfg.database.host;
+        port = cfg.database.port;
+      });
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openPorts [
+      config.services.zammad.port
+      config.services.zammad.websocketPort
+    ];
+
+    users.users.zammad = {
+      isSystemUser = true;
+      home = cfg.dataDir;
+      group = "zammad";
+    };
+
+    users.groups.zammad = { };
+
+    assertions = [
+      {
+        assertion = cfg.database.createLocally -> cfg.database.user == "zammad";
+        message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
+      }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.zammad.database.createLocally is set to true";
+      }
+    ];
+
+    services.mysql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "MySQL") {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "PostgreSQL") {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.services.zammad-web = {
+      inherit environment;
+      serviceConfig = serviceConfig // {
+        # loading all the gems takes time
+        TimeoutStartSec = 1200;
+      };
+      after = [
+        "network.target"
+        "postgresql.service"
+      ];
+      requires = [
+        "postgresql.service"
+      ];
+      description = "Zammad web";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        # Blindly copy the whole project here.
+        chmod -R +w .
+        rm -rf ./public/assets/*
+        rm -rf ./tmp/*
+        rm -rf ./log/*
+        cp -r --no-preserve=owner ${cfg.package}/* .
+        chmod -R +w .
+        # config file
+        cp ${databaseConfig} ./config/database.yml
+        chmod -R +w .
+        ${optionalString (cfg.database.passwordFile != null) ''
+        {
+          echo -n "  password: "
+          cat ${cfg.database.passwordFile}
+        } >> ./config/database.yml
+        ''}
+        ${optionalString (cfg.secretKeyBaseFile != null) ''
+        {
+          echo "production: "
+          echo -n "  secret_key_base: "
+          cat ${cfg.secretKeyBaseFile}
+        } > ./config/secrets.yml
+        ''}
+
+        if [ `${config.services.postgresql.package}/bin/psql \
+                  --host ${cfg.database.host} \
+                  ${optionalString
+                    (cfg.database.port != null)
+                    "--port ${toString cfg.database.port}"} \
+                  --username ${cfg.database.user} \
+                  --dbname ${cfg.database.name} \
+                  --command "SELECT COUNT(*) FROM pg_class c \
+                            JOIN pg_namespace s ON s.oid = c.relnamespace \
+                            WHERE s.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') \
+                              AND s.nspname NOT LIKE 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
+          echo "Initialize database"
+          ./bin/rake --no-system db:migrate
+          ./bin/rake --no-system db:seed
+        else
+          echo "Migrate database"
+          ./bin/rake --no-system db:migrate
+        fi
+        echo "Done"
+      '';
+      script = "./script/rails server -b ${cfg.host} -p ${toString cfg.port}";
+    };
+
+    systemd.services.zammad-websocket = {
+      inherit serviceConfig environment;
+      after = [ "zammad-web.service" ];
+      requires = [ "zammad-web.service" ];
+      description = "Zammad websocket";
+      wantedBy = [ "multi-user.target" ];
+      script = "./script/websocket-server.rb -b ${cfg.host} -p ${toString cfg.websocketPort} start";
+    };
+
+    systemd.services.zammad-scheduler = {
+      inherit environment;
+      serviceConfig = serviceConfig // { Type = "forking"; };
+      after = [ "zammad-web.service" ];
+      requires = [ "zammad-web.service" ];
+      description = "Zammad scheduler";
+      wantedBy = [ "multi-user.target" ];
+      script = "./script/scheduler.rb start";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ garbas taeer ];
+}
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
index e898f3260585..6be23f39754e 100644
--- a/nixos/modules/services/hardware/udisks2.nix
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -32,6 +32,8 @@ with lib;
 
     environment.systemPackages = [ pkgs.udisks2 ];
 
+    security.polkit.enable = true;
+
     services.dbus.packages = [ pkgs.udisks2 ];
 
     systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
diff --git a/nixos/modules/services/misc/zigbee2mqtt.nix b/nixos/modules/services/home-automation/zigbee2mqtt.nix
index ff6d595e5a6e..ff6d595e5a6e 100644
--- a/nixos/modules/services/misc/zigbee2mqtt.nix
+++ b/nixos/modules/services/home-automation/zigbee2mqtt.nix
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 77e4fc395981..082cf92ff4ef 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -167,7 +167,6 @@ in
 
     systemd.services.logrotate = {
       description = "Logrotate Service";
-      wantedBy = [ "multi-user.target" ];
       startAt = "hourly";
 
       serviceConfig = {
diff --git a/nixos/modules/services/misc/matrix-synapse-log_config.yaml b/nixos/modules/services/matrix/matrix-synapse-log_config.yaml
index d85bdd1208f9..d85bdd1208f9 100644
--- a/nixos/modules/services/misc/matrix-synapse-log_config.yaml
+++ b/nixos/modules/services/matrix/matrix-synapse-log_config.yaml
diff --git a/nixos/modules/services/matrix/matrix-synapse.nix b/nixos/modules/services/matrix/matrix-synapse.nix
new file mode 100644
index 000000000000..c4d14dbd547e
--- /dev/null
+++ b/nixos/modules/services/matrix/matrix-synapse.nix
@@ -0,0 +1,773 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.matrix-synapse;
+  format = pkgs.formats.yaml {};
+
+  # remove null values from the final configuration
+  finalSettings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
+  configFile = format.generate "homeserver.yaml" finalSettings;
+  logConfigFile = format.generate "log_config.yaml" cfg.logConfig;
+
+  pluginsEnv = cfg.package.python.buildEnv.override {
+    extraLibs = cfg.plugins;
+  };
+
+  usePostgresql = cfg.settings.database.name == "psycopg2";
+  hasLocalPostgresDB = let args = cfg.settings.database.args; in
+    usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
+
+  registerNewMatrixUser =
+    let
+      isIpv6 = x: lib.length (lib.splitString ":" x) > 1;
+      listener =
+        lib.findFirst (
+          listener: lib.any (
+            resource: lib.any (
+              name: name == "client"
+            ) resource.names
+          ) listener.resources
+        ) (lib.last cfg.settings.listeners) cfg.settings.listeners;
+        # FIXME: Handle cases with missing client listener properly,
+        # don't rely on lib.last, this will not work.
+
+      # add a tail, so that without any bind_addresses we still have a useable address
+      bindAddress = head (listener.bind_addresses ++ [ "127.0.0.1" ]);
+      listenerProtocol = if listener.tls
+        then "https"
+        else "http";
+    in
+    pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" ''
+      exec ${cfg.package}/bin/register_new_matrix_user \
+        $@ \
+        ${lib.concatMapStringsSep " " (x: "-c ${x}") ([ configFile ] ++ cfg.extraConfigFiles)} \
+        "${listenerProtocol}://${
+          if (isIpv6 bindAddress) then
+            "[${bindAddress}]"
+          else
+            "${bindAddress}"
+        }:${builtins.toString listener.port}/"
+    '';
+in {
+
+  imports = [
+
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
+      The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
+      as the behavior is now obsolete.
+    '')
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
+      Database configuration must be done manually. An exemplary setup is demonstrated in
+      <nixpkgs/nixos/tests/matrix-synapse.nix>
+    '')
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
+      You may add additional event types via
+      `services.matrix-synapse.room_prejoin_state.additional_event_types` and
+      disable the default events via
+      `services.matrix-synapse.room_prejoin_state.disable_default_event_types`.
+    '')
+
+    # options that don't exist in synapse anymore
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "bind_host" ] "Use listener settings instead." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "bind_port" ] "Use listener settings instead." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "expire_access_tokens" ] "" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "no_tls" ] "It is no longer supported by synapse." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_dh_param_path" ] "It was removed from synapse." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "unsecure_port" ] "Use settings.listeners instead." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "user_creation_max_duration" ] "It is no longer supported by synapse." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "verbose" ] "Use a log config instead." )
+
+    # options that were moved into rfc42 style settigns
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "app_service_config_files" ] "Use settings.app_service_config_Files instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "database_args" ] "Use settings.database.args instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "database_name" ] "Use settings.database.args.database instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "database_type" ] "Use settings.database.name instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "database_user" ] "Use settings.database.args.user instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "dynamic_thumbnails" ] "Use settings.dynamic_thumbnails instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_metrics" ] "Use settings.enable_metrics instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_registration" ] "Use settings.enable_registration instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "extraConfig" ] "Use settings instead." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "listeners" ] "Use settings.listeners instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "logConfig" ] "Use settings.log_config instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "max_image_pixels" ] "Use settings.max_image_pixels instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "max_upload_size" ] "Use settings.max_upload_size instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "presence" "enabled" ] "Use settings.presence.enabled instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "public_baseurl" ] "Use settings.public_baseurl instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "report_stats" ] "Use settings.report_stats instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "server_name" ] "Use settings.server_name instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "servers" ] "Use settings.trusted_key_servers instead." )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_certificate_path" ] "Use settings.tls_certificate_path instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "tls_private_key_path" ] "Use settings.tls_private_key_path instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_shared_secret" ] "Use settings.turn_shared_secret instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_uris" ] "Use settings.turn_uris instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "turn_user_lifetime" ] "Use settings.turn_user_lifetime instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_enabled" ] "Use settings.url_preview_enabled instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_ip_range_blacklist" ] "Use settings.url_preview_ip_range_blacklist instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_ip_range_whitelist" ] "Use settings.url_preview_ip_range_whitelist instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "url_preview_url_blacklist" ] "Use settings.url_preview_url_blacklist instead" )
+
+    # options that are too specific to mention them explicitly in settings
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "account_threepid_delegates" "email" ] "Use settings.account_threepid_delegates.email instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "account_threepid_delegates" "msisdn" ] "Use settings.account_threepid_delegates.msisdn instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "allow_guest_access" ] "Use settings.allow_guest_access instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "bcrypt_rounds" ] "Use settings.bcrypt_rounds instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "enable_registration_captcha" ] "Use settings.enable_registration_captcha instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "event_cache_size" ] "Use settings.event_cache_size instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_concurrent" ] "Use settings.rc_federation.concurrent instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_reject_limit" ] "Use settings.rc_federation.reject_limit instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_sleep_delay" ] "Use settings.rc_federation.sleep_delay instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_sleep_limit" ] "Use settings.rc_federation.sleep_limit instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "federation_rc_window_size" ] "Use settings.rc_federation.window_size instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "key_refresh_interval" ] "Use settings.key_refresh_interval instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "rc_messages_burst_count" ] "Use settings.rc_messages.burst_count instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "rc_messages_per_second" ] "Use settings.rc_messages.per_second instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "recaptcha_private_key" ] "Use settings.recaptcha_private_key instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "recaptcha_public_key" ] "Use settings.recaptcha_public_key instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "redaction_retention_period" ] "Use settings.redaction_retention_period instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "room_prejoin_state" "additional_event_types" ] "Use settings.room_prejoin_state.additional_event_types instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "room_prejoin_state" "disable_default_event_types" ] "Use settings.room_prejoin-state.disable_default_event_types instead" )
+
+    # Options that should be passed via extraConfigFiles, so they are not persisted into the nix store
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "macaroon_secret_key" ] "Pass this value via extraConfigFiles instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "registration_shared_secret" ] "Pass this value via extraConfigFiles instead" )
+
+  ];
+
+  options = {
+    services.matrix-synapse = {
+      enable = mkEnableOption "matrix.org synapse";
+
+      configFile = mkOption {
+        type = types.str;
+        readOnly = true;
+        description = ''
+          Path to the configuration file on the target system. Useful to configure e.g. workers
+          that also need this.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.matrix-synapse;
+        defaultText = literalExpression "pkgs.matrix-synapse";
+        description = ''
+          Overridable attribute of the matrix synapse server package to use.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = literalExpression ''
+          with config.services.matrix-synapse.package.plugins; [
+            matrix-synapse-ldap3
+            matrix-synapse-pam
+          ];
+        '';
+        description = ''
+          List of additional Matrix plugins to make available.
+        '';
+      };
+
+      withJemalloc = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to preload jemalloc to reduce memory fragmentation and overall usage.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/matrix-synapse";
+        description = ''
+          The directory where matrix-synapse stores its stateful data such as
+          certificates, media and uploads.
+        '';
+      };
+
+      settings = mkOption {
+        default = {};
+        description = ''
+          The primary synapse configuration. See the
+          <link xlink:href="https://github.com/matrix-org/synapse/blob/v${cfg.package.version}/docs/sample_config.yaml">sample configuration</link>
+          for possible values.
+
+          Secrets should be passed in by using the <literal>extraConfigFiles</literal> option.
+        '';
+        type = with types; submodule {
+          freeformType = format.type;
+          options = {
+            # This is a reduced set of popular options and defaults
+            # Do not add every available option here, they can be specified
+            # by the user at their own discretion. This is a freeform type!
+
+            server_name = mkOption {
+              type = types.str;
+              example = "example.com";
+              default = config.networking.hostName;
+              defaultText = literalExpression "config.networking.hostName";
+              description = ''
+                The domain name of the server, with optional explicit port.
+                This is used by remote servers to look up the server address.
+                This is also the last part of your UserID.
+
+                The server_name cannot be changed later so it is important to configure this correctly before you start Synapse.
+              '';
+            };
+
+            enable_registration = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Enable registration for new users.
+              '';
+            };
+
+            registration_shared_secret = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                If set, allows registration by anyone who also has the shared
+                secret, even if registration is otherwise disabled.
+
+                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+              '';
+            };
+
+            macaroon_secret_key = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                Secret key for authentication tokens. If none is specified,
+                the registration_shared_secret is used, if one is given; otherwise,
+                a secret key is derived from the signing key.
+
+                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+              '';
+            };
+
+            enable_metrics = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Enable collection and rendering of performance metrics
+              '';
+            };
+
+            report_stats = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether or not to report anonymized homeserver usage statistics.
+              '';
+            };
+
+            signing_key_path = mkOption {
+              type = types.path;
+              default = "${cfg.dataDir}/homeserver.signing.key";
+              description = ''
+                Path to the signing key to sign messages with.
+              '';
+            };
+
+            pid_file = mkOption {
+              type = types.path;
+              default = "/run/matrix-synapse.pid";
+              readOnly = true;
+              description = ''
+                The file to store the PID in.
+              '';
+            };
+
+            log_config = mkOption {
+              type = types.path;
+              default = ./matrix-synapse-log_config.yaml;
+              description = ''
+                The file that holds the logging configuration.
+              '';
+            };
+
+            media_store_path = mkOption {
+              type = types.path;
+              default = if lib.versionAtLeast config.system.stateVersion "22.05"
+                then "${cfg.dataDir}/media_store"
+                else "${cfg.dataDir}/media";
+              description = ''
+                Directory where uploaded images and attachments are stored.
+              '';
+            };
+
+            public_baseurl = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "https://example.com:8448/";
+              description = ''
+                The public-facing base URL for the client API (not including _matrix/...)
+              '';
+            };
+
+            tls_certificate_path = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "/var/lib/acme/example.com/fullchain.pem";
+              description = ''
+                PEM encoded X509 certificate for TLS.
+                You can replace the self-signed certificate that synapse
+                autogenerates on launch with your own SSL certificate + key pair
+                if you like.  Any required intermediary certificates can be
+                appended after the primary certificate in hierarchical order.
+              '';
+            };
+
+            tls_private_key_path = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "/var/lib/acme/example.com/key.pem";
+              description = ''
+                PEM encoded private key for TLS. Specify null if synapse is not
+                speaking TLS directly.
+              '';
+            };
+
+            presence.enabled = mkOption {
+              type = types.bool;
+              default = true;
+              example = false;
+              description = ''
+                Whether to enable presence tracking.
+
+                Presence tracking allows users to see the state (e.g online/offline)
+                of other local and remote users.
+              '';
+            };
+
+            listeners = mkOption {
+              type = types.listOf (types.submodule {
+                options = {
+                  port = mkOption {
+                    type = types.port;
+                    example = 8448;
+                    description = ''
+                      The port to listen for HTTP(S) requests on.
+                    '';
+                  };
+
+                  bind_addresses = mkOption {
+                    type = types.listOf types.str;
+                    default = [
+                      "::1"
+                      "127.0.0.1"
+                    ];
+                    example = literalExpression ''
+                    [
+                      "::"
+                      "0.0.0.0"
+                    ]
+                    '';
+                    description = ''
+                     IP addresses to bind the listener to.
+                    '';
+                  };
+
+                  type = mkOption {
+                    type = types.enum [
+                      "http"
+                      "manhole"
+                      "metrics"
+                      "replication"
+                    ];
+                    default = "http";
+                    example = "metrics";
+                    description = ''
+                      The type of the listener, usually http.
+                    '';
+                  };
+
+                  tls = mkOption {
+                    type = types.bool;
+                    default = true;
+                    example = false;
+                    description = ''
+                      Whether to enable TLS on the listener socket.
+                    '';
+                  };
+
+                  x_forwarded = mkOption {
+                    type = types.bool;
+                    default = false;
+                    example = true;
+                    description = ''
+                      Use the X-Forwarded-For (XFF) header as the client IP and not the
+                      actual client IP.
+                    '';
+                  };
+
+                  resources = mkOption {
+                    type = types.listOf (types.submodule {
+                      options = {
+                        names = mkOption {
+                          type = types.listOf (types.enum [
+                            "client"
+                            "consent"
+                            "federation"
+                            "keys"
+                            "media"
+                            "metrics"
+                            "openid"
+                            "replication"
+                            "static"
+                          ]);
+                          description = ''
+                            List of resources to host on this listener.
+                          '';
+                          example = [
+                            "client"
+                          ];
+                        };
+                        compress = mkOption {
+                          type = types.bool;
+                          description = ''
+                            Should synapse compress HTTP responses to clients that support it?
+                            This should be disabled if running synapse behind a load balancer
+                            that can do automatic compression.
+                          '';
+                        };
+                      };
+                    });
+                    description = ''
+                      List of HTTP resources to serve on this listener.
+                    '';
+                  };
+                };
+              });
+              default = [ {
+                port = 8008;
+                bind_addresses = [ "127.0.0.1" ];
+                type = "http";
+                tls = false;
+                x_forwarded = true;
+                resources = [ {
+                  names = [ "client" ];
+                  compress = true;
+                } {
+                  names = [ "federation" ];
+                  compress = false;
+                } ];
+              } ];
+              description = ''
+                List of ports that Synapse should listen on, their purpose and their configuration.
+              '';
+            };
+
+            database.name = mkOption {
+              type = types.enum [
+                "sqlite3"
+                "psycopg2"
+              ];
+              default = if versionAtLeast config.system.stateVersion "18.03"
+                then "psycopg2"
+                else "sqlite3";
+               defaultText = literalExpression ''
+                if versionAtLeast config.system.stateVersion "18.03"
+                then "psycopg2"
+                else "sqlite3"
+              '';
+              description = ''
+                The database engine name. Can be sqlite3 or psycopg2.
+              '';
+            };
+
+            database.args.database = mkOption {
+              type = types.str;
+              default = {
+                sqlite3 = "${cfg.dataDir}/homeserver.db";
+                psycopg2 = "matrix-synapse";
+              }.${cfg.settings.database.name};
+              defaultText = literalExpression ''
+              {
+                sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
+                psycopg2 = "matrix-synapse";
+              }.''${${options.services.matrix-synapse.settings}.database.name};
+              '';
+              description = ''
+                Name of the database when using the psycopg2 backend,
+                path to the database location when using sqlite3.
+              '';
+            };
+
+            database.args.user = mkOption {
+              type = types.nullOr types.str;
+              default = {
+                sqlite3 = null;
+                psycopg2 = "matrix-synapse";
+              }.${cfg.settings.database.name};
+              description = ''
+                Username to connect with psycopg2, set to null
+                when using sqlite3.
+              '';
+            };
+
+            url_preview_enabled = mkOption {
+              type = types.bool;
+              default = true;
+              example = false;
+              description = ''
+                Is the preview URL API enabled?  If enabled, you *must* specify an
+                explicit url_preview_ip_range_blacklist of IPs that the spider is
+                denied from accessing.
+              '';
+            };
+
+            url_preview_ip_range_blacklist = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "10.0.0.0/8"
+                "100.64.0.0/10"
+                "127.0.0.0/8"
+                "169.254.0.0/16"
+                "172.16.0.0/12"
+                "192.0.0.0/24"
+                "192.0.2.0/24"
+                "192.168.0.0/16"
+                "192.88.99.0/24"
+                "198.18.0.0/15"
+                "198.51.100.0/24"
+                "2001:db8::/32"
+                "203.0.113.0/24"
+                "224.0.0.0/4"
+                "::1/128"
+                "fc00::/7"
+                "fe80::/10"
+                "fec0::/10"
+                "ff00::/8"
+              ];
+              description = ''
+                List of IP address CIDR ranges that the URL preview spider is denied
+                from accessing.
+              '';
+            };
+
+            url_preview_ip_range_whitelist = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = ''
+                List of IP address CIDR ranges that the URL preview spider is allowed
+                to access even if they are specified in url_preview_ip_range_blacklist.
+              '';
+            };
+
+            url_preview_url_blacklist = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = ''
+                Optional list of URL matches that the URL preview spider is
+                denied from accessing.
+              '';
+            };
+
+            max_upload_size = mkOption {
+              type = types.str;
+              default = "50M";
+              example = "100M";
+              description = ''
+                The largest allowed upload size in bytes
+              '';
+            };
+
+            max_image_pixels = mkOption {
+              type = types.str;
+              default = "32M";
+              example = "64M";
+              description = ''
+                Maximum number of pixels that will be thumbnailed
+              '';
+            };
+
+            dynamic_thumbnails = mkOption {
+              type = types.bool;
+              default = false;
+              example = true;
+              description = ''
+                Whether to generate new thumbnails on the fly to precisely match
+                the resolution requested by the client. If true then whenever
+                a new resolution is requested by the client the server will
+                generate a new thumbnail. If false the server will pick a thumbnail
+                from a precalculated list.
+              '';
+            };
+
+            turn_uris = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [
+                "turn:turn.example.com:3487?transport=udp"
+                "turn:turn.example.com:3487?transport=tcp"
+                "turns:turn.example.com:5349?transport=udp"
+                "turns:turn.example.com:5349?transport=tcp"
+              ];
+              description = ''
+                The public URIs of the TURN server to give to clients
+              '';
+            };
+            turn_shared_secret = mkOption {
+              type = types.str;
+              default = "";
+              example = literalExpression ''
+                config.services.coturn.static-auth-secret
+              '';
+              description = ''
+                The shared secret used to compute passwords for the TURN server.
+
+                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+              '';
+            };
+
+            trusted_key_servers = mkOption {
+              type = types.listOf (types.submodule {
+                options = {
+                  server_name = mkOption {
+                    type = types.str;
+                    example = "matrix.org";
+                    description = ''
+                      Hostname of the trusted server.
+                    '';
+                  };
+
+                  verify_keys = mkOption {
+                    type = types.nullOr (types.attrsOf types.str);
+                    default = null;
+                    example = literalExpression ''
+                      {
+                        "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
+                      }
+                    '';
+                    description = ''
+                      Attribute set from key id to base64 encoded public key.
+
+                      If specified synapse will check that the response is signed
+                      by at least one of the given keys.
+                    '';
+                  };
+                };
+              });
+              default = [ {
+                server_name = "matrix.org";
+                verify_keys = {
+                  "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
+                };
+              } ];
+              description = ''
+                The trusted servers to download signing keys from.
+              '';
+            };
+
+            app_service_config_files = mkOption {
+              type = types.listOf types.path;
+              default = [ ];
+              description = ''
+                A list of application service config file to use
+              '';
+            };
+
+          };
+        };
+      };
+
+      extraConfigFiles = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          Extra config files to include.
+
+          The configuration files will be included based on the command line
+          argument --config-path. This allows to configure secrets without
+          having to go through the Nix store, e.g. based on deployment keys if
+          NixOps is in use.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
+        message = ''
+          Cannot deploy matrix-synapse with a configuration for a local postgresql database
+            and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
+            database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
+            further reference).
+
+            If you
+            - try to deploy a fresh synapse, you need to configure the database yourself. An example
+              for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
+            - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
+              to your configuration.
+
+          For further information about this update, please read the release-notes of 20.03 carefully.
+        '';
+      }
+    ];
+
+    services.matrix-synapse.configFile = configFile;
+
+    users.users.matrix-synapse = {
+      group = "matrix-synapse";
+      home = cfg.dataDir;
+      createHome = true;
+      shell = "${pkgs.bash}/bin/bash";
+      uid = config.ids.uids.matrix-synapse;
+    };
+
+    users.groups.matrix-synapse = {
+      gid = config.ids.gids.matrix-synapse;
+    };
+
+    systemd.services.matrix-synapse = {
+      description = "Synapse Matrix homeserver";
+      after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        ${cfg.package}/bin/synapse_homeserver \
+          --config-path ${configFile} \
+          --keys-directory ${cfg.dataDir} \
+          --generate-keys
+      '';
+      environment = {
+        PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
+      } // optionalAttrs (cfg.withJemalloc) {
+        LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
+      };
+      serviceConfig = {
+        Type = "notify";
+        User = "matrix-synapse";
+        Group = "matrix-synapse";
+        WorkingDirectory = cfg.dataDir;
+        ExecStartPre = [ ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
+          chown matrix-synapse:matrix-synapse ${cfg.dataDir}/homeserver.signing.key
+          chmod 0600 ${cfg.dataDir}/homeserver.signing.key
+        '')) ];
+        ExecStart = ''
+          ${cfg.package}/bin/synapse_homeserver \
+            ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+            --keys-directory ${cfg.dataDir}
+        '';
+        ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
+        Restart = "on-failure";
+        UMask = "0077";
+      };
+    };
+
+    environment.systemPackages = [ registerNewMatrixUser ];
+  };
+
+  meta = {
+    buildDocsInSandbox = false;
+    doc = ./matrix-synapse.xml;
+    maintainers = teams.matrix.members;
+  };
+
+}
diff --git a/nixos/modules/services/misc/matrix-synapse.xml b/nixos/modules/services/matrix/matrix-synapse.xml
index 41a56df0f2b5..cdc4b4de1a73 100644
--- a/nixos/modules/services/misc/matrix-synapse.xml
+++ b/nixos/modules/services/matrix/matrix-synapse.xml
@@ -115,20 +115,21 @@ in {
   };
   services.matrix-synapse = {
     <link linkend="opt-services.matrix-synapse.enable">enable</link> = true;
-    <link linkend="opt-services.matrix-synapse.server_name">server_name</link> = config.networking.domain;
-    <link linkend="opt-services.matrix-synapse.listeners">listeners</link> = [
+    <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.listeners._.port">port</link> = 8008;
-        <link linkend="opt-services.matrix-synapse.listeners._.bind_address">bind_address</link> = "::1";
-        <link linkend="opt-services.matrix-synapse.listeners._.type">type</link> = "http";
-        <link linkend="opt-services.matrix-synapse.listeners._.tls">tls</link> = false;
-        <link linkend="opt-services.matrix-synapse.listeners._.x_forwarded">x_forwarded</link> = true;
-        <link linkend="opt-services.matrix-synapse.listeners._.resources">resources</link> = [
-          {
-            <link linkend="opt-services.matrix-synapse.listeners._.resources._.names">names</link> = [ "client" "federation" ];
-            <link linkend="opt-services.matrix-synapse.listeners._.resources._.compress">compress</link> = false;
-          }
-        ];
+        <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
+        <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_address</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._.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;
+        } ];
       }
     ];
   };
@@ -151,11 +152,11 @@ in {
 
   <para>
    If you want to run a server with public registration by anybody, you can
-   then enable <literal><link linkend="opt-services.matrix-synapse.enable_registration">services.matrix-synapse.enable_registration</link> =
+   then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.enable_registration</link> =
    true;</literal>. Otherwise, or you can generate a registration secret with
    <command>pwgen -s 64 1</command> and set it with
-   <option><link linkend="opt-services.matrix-synapse.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>. To
-   create a new user or admin, run the following after you have set the secret
+   <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>.
+   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
@@ -170,7 +171,7 @@ Success!
    <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.registration_shared_secret">registration_shared_secret</link>
+   <link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">registration_shared_secret</link>
    option until a better solution for NixOS is in place.
   </para>
  </section>
diff --git a/nixos/modules/services/misc/input-remapper.nix b/nixos/modules/services/misc/input-remapper.nix
index c2da0d616a31..f5fb2bf53086 100644
--- a/nixos/modules/services/misc/input-remapper.nix
+++ b/nixos/modules/services/misc/input-remapper.nix
@@ -7,23 +7,24 @@ let cfg = config.services.input-remapper; in
   options = {
     services.input-remapper = {
       enable = mkEnableOption "input-remapper, an easy to use tool to change the mapping of your input device buttons.";
-      package = mkOption {
-        type = types.package;
-        default = pkgs.input-remapper;
-        defaultText = literalExpression "pkgs.input-remapper";
-        description = ''
-          The input-remapper package to use.
-        '';
+      package = options.mkPackageOption pkgs "input-remapper" { };
+      enableUdevRules = mkEnableOption "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140";
+      serviceWantedBy = mkOption {
+        default = [ "graphical.target" ];
+        example = [ "multi-user.target" ];
+        type = types.listOf types.str;
+        description = "Specifies the WantedBy setting for the input-remapper service.";
       };
     };
   };
 
   config = mkIf cfg.enable {
-    # FIXME: udev rule hangs sometimes when lots of devices connected, so let's not use it
-    # config.services.udev.packages = mapper-pkg;
-    services.dbus.packages = cfg.package;
-    systemd.packages = cfg.package;
-    environment.systemPackages = cfg.package;
-    systemd.services.input-remapper.wantedBy = [ "graphical.target" ];
+    services.udev.packages = mkIf cfg.enableUdevRules [ cfg.package ];
+    services.dbus.packages = [ cfg.package ];
+    systemd.packages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package ];
+    systemd.services.input-remapper.wantedBy = cfg.serviceWantedBy;
   };
+
+  meta.maintainers = with lib.maintainers; [ LunNova ];
 }
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
deleted file mode 100644
index feca4c5465ff..000000000000
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ /dev/null
@@ -1,844 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.matrix-synapse;
-  opt = options.services.matrix-synapse;
-  pg = config.services.postgresql;
-  usePostgresql = cfg.database_type == "psycopg2";
-  logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
-  mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
-  mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
-  pluginsEnv = cfg.package.python.buildEnv.override {
-    extraLibs = cfg.plugins;
-  };
-  configFile = pkgs.writeText "homeserver.yaml" ''
-${optionalString (cfg.tls_certificate_path != null) ''
-tls_certificate_path: "${cfg.tls_certificate_path}"
-''}
-${optionalString (cfg.tls_private_key_path != null) ''
-tls_private_key_path: "${cfg.tls_private_key_path}"
-''}
-${optionalString (cfg.tls_dh_params_path != null) ''
-tls_dh_params_path: "${cfg.tls_dh_params_path}"
-''}
-no_tls: ${boolToString cfg.no_tls}
-${optionalString (cfg.bind_port != null) ''
-bind_port: ${toString cfg.bind_port}
-''}
-${optionalString (cfg.unsecure_port != null) ''
-unsecure_port: ${toString cfg.unsecure_port}
-''}
-${optionalString (cfg.bind_host != null) ''
-bind_host: "${cfg.bind_host}"
-''}
-server_name: "${cfg.server_name}"
-pid_file: "/run/matrix-synapse.pid"
-${optionalString (cfg.public_baseurl != null) ''
-public_baseurl: "${cfg.public_baseurl}"
-''}
-listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
-database: {
-  name: "${cfg.database_type}",
-  args: {
-    ${concatStringsSep ",\n    " (
-      mapAttrsToList (n: v: "\"${n}\": ${builtins.toJSON v}") cfg.database_args
-    )}
-  }
-}
-event_cache_size: "${cfg.event_cache_size}"
-verbose: ${cfg.verbose}
-log_config: "${logConfigFile}"
-rc_messages_per_second: ${cfg.rc_messages_per_second}
-rc_message_burst_count: ${cfg.rc_message_burst_count}
-federation_rc_window_size: ${cfg.federation_rc_window_size}
-federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
-federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
-federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
-federation_rc_concurrent: ${cfg.federation_rc_concurrent}
-media_store_path: "${cfg.dataDir}/media"
-uploads_path: "${cfg.dataDir}/uploads"
-max_upload_size: "${cfg.max_upload_size}"
-max_image_pixels: "${cfg.max_image_pixels}"
-dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails}
-url_preview_enabled: ${boolToString cfg.url_preview_enabled}
-${optionalString (cfg.url_preview_enabled == true) ''
-url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist}
-url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist}
-url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist}
-''}
-recaptcha_private_key: "${cfg.recaptcha_private_key}"
-recaptcha_public_key: "${cfg.recaptcha_public_key}"
-enable_registration_captcha: ${boolToString cfg.enable_registration_captcha}
-turn_uris: ${builtins.toJSON cfg.turn_uris}
-turn_shared_secret: "${cfg.turn_shared_secret}"
-enable_registration: ${boolToString cfg.enable_registration}
-${optionalString (cfg.registration_shared_secret != null) ''
-registration_shared_secret: "${cfg.registration_shared_secret}"
-''}
-recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
-turn_user_lifetime: "${cfg.turn_user_lifetime}"
-user_creation_max_duration: ${cfg.user_creation_max_duration}
-bcrypt_rounds: ${cfg.bcrypt_rounds}
-allow_guest_access: ${boolToString cfg.allow_guest_access}
-
-account_threepid_delegates:
-  ${optionalString (cfg.account_threepid_delegates.email != null) "email: ${cfg.account_threepid_delegates.email}"}
-  ${optionalString (cfg.account_threepid_delegates.msisdn != null) "msisdn: ${cfg.account_threepid_delegates.msisdn}"}
-
-room_prejoin_state:
-  disable_default_event_types: ${boolToString cfg.room_prejoin_state.disable_default_event_types}
-  additional_event_types: ${builtins.toJSON cfg.room_prejoin_state.additional_event_types}
-${optionalString (cfg.macaroon_secret_key != null) ''
-  macaroon_secret_key: "${cfg.macaroon_secret_key}"
-''}
-expire_access_token: ${boolToString cfg.expire_access_token}
-enable_metrics: ${boolToString cfg.enable_metrics}
-report_stats: ${boolToString cfg.report_stats}
-signing_key_path: "${cfg.dataDir}/homeserver.signing.key"
-key_refresh_interval: "${cfg.key_refresh_interval}"
-perspectives:
-  servers: {
-    ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
-    "${n}": {
-      "verify_keys": {
-        ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
-        "${n}": {
-          "key": "${v}"
-        }'') v)}
-      }
-    '') cfg.servers)}
-    }
-  }
-redaction_retention_period: ${toString cfg.redaction_retention_period}
-app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
-
-${cfg.extraConfig}
-'';
-
-  hasLocalPostgresDB = let args = cfg.database_args; in
-    usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
-
-  registerNewMatrixUser =
-    let
-      isIpv6 = x: lib.length (lib.splitString ":" x) > 1;
-      listener =
-        lib.findFirst (
-          listener: lib.any (
-            resource: lib.any (
-              name: name == "client"
-            ) resource.names
-          ) listener.resources
-        ) (lib.last cfg.listeners) cfg.listeners;
-    in
-    pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" ''
-      exec ${cfg.package}/bin/register_new_matrix_user \
-        $@ \
-        ${lib.concatMapStringsSep " " (x: "-c ${x}") ([ configFile ] ++ cfg.extraConfigFiles)} \
-        "${listener.type}://${
-          if (isIpv6 listener.bind_address) then
-            "[${listener.bind_address}]"
-          else
-            "${listener.bind_address}"
-        }:${builtins.toString listener.port}/"
-    '';
-in {
-  options = {
-    services.matrix-synapse = {
-      enable = mkEnableOption "matrix.org synapse";
-      configFile = mkOption {
-        type = types.str;
-        readOnly = true;
-        description = ''
-          Path to the configuration file on the target system. Useful to configure e.g. workers
-          that also need this.
-        '';
-      };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.matrix-synapse;
-        defaultText = literalExpression "pkgs.matrix-synapse";
-        description = ''
-          Overridable attribute of the matrix synapse server package to use.
-        '';
-      };
-      plugins = mkOption {
-        type = types.listOf types.package;
-        default = [ ];
-        example = literalExpression ''
-          with config.services.matrix-synapse.package.plugins; [
-            matrix-synapse-ldap3
-            matrix-synapse-pam
-          ];
-        '';
-        description = ''
-          List of additional Matrix plugins to make available.
-        '';
-      };
-      withJemalloc = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to preload jemalloc to reduce memory fragmentation and overall usage.
-        '';
-      };
-      no_tls = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Don't bind to the https port
-        '';
-      };
-      bind_port = mkOption {
-        type = types.nullOr types.int;
-        default = null;
-        example = 8448;
-        description = ''
-          DEPRECATED: Use listeners instead.
-          The port to listen for HTTPS requests on.
-          For when matrix traffic is sent directly to synapse.
-        '';
-      };
-      unsecure_port = mkOption {
-        type = types.nullOr types.int;
-        default = null;
-        example = 8008;
-        description = ''
-          DEPRECATED: Use listeners instead.
-          The port to listen for HTTP requests on.
-          For when matrix traffic passes through loadbalancer that unwraps TLS.
-        '';
-      };
-      bind_host = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          DEPRECATED: Use listeners instead.
-          Local interface to listen on.
-          The empty string will cause synapse to listen on all interfaces.
-        '';
-      };
-      tls_certificate_path = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/matrix-synapse/homeserver.tls.crt";
-        description = ''
-          PEM encoded X509 certificate for TLS.
-          You can replace the self-signed certificate that synapse
-          autogenerates on launch with your own SSL certificate + key pair
-          if you like.  Any required intermediary certificates can be
-          appended after the primary certificate in hierarchical order.
-        '';
-      };
-      tls_private_key_path = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/matrix-synapse/homeserver.tls.key";
-        description = ''
-          PEM encoded private key for TLS. Specify null if synapse is not
-          speaking TLS directly.
-        '';
-      };
-      tls_dh_params_path = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/matrix-synapse/homeserver.tls.dh";
-        description = ''
-          PEM dh parameters for ephemeral keys
-        '';
-      };
-      server_name = mkOption {
-        type = types.str;
-        example = "example.com";
-        default = config.networking.hostName;
-        defaultText = literalExpression "config.networking.hostName";
-        description = ''
-          The domain name of the server, with optional explicit port.
-          This is used by remote servers to look up the server address.
-          This is also the last part of your UserID.
-
-          The server_name cannot be changed later so it is important to configure this correctly before you start Synapse.
-        '';
-      };
-      public_baseurl = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "https://example.com:8448/";
-        description = ''
-          The public-facing base URL for the client API (not including _matrix/...)
-        '';
-      };
-      listeners = mkOption {
-        type = types.listOf (types.submodule {
-          options = {
-            port = mkOption {
-              type = types.port;
-              example = 8448;
-              description = ''
-                The port to listen for HTTP(S) requests on.
-              '';
-            };
-            bind_address = mkOption {
-              type = types.str;
-              default = "";
-              example = "203.0.113.42";
-              description = ''
-                Local interface to listen on.
-                The empty string will cause synapse to listen on all interfaces.
-              '';
-            };
-            type = mkOption {
-              type = types.str;
-              default = "http";
-              description = ''
-                Type of listener.
-              '';
-            };
-            tls = mkOption {
-              type = types.bool;
-              default = true;
-              description = ''
-                Whether to listen for HTTPS connections rather than HTTP.
-              '';
-            };
-            x_forwarded = mkOption {
-              type = types.bool;
-              default = false;
-              description = ''
-                Use the X-Forwarded-For (XFF) header as the client IP and not the
-                actual client IP.
-              '';
-            };
-            resources = mkOption {
-              type = types.listOf (types.submodule {
-                options = {
-                  names = mkOption {
-                    type = types.listOf types.str;
-                    description = ''
-                      List of resources to host on this listener.
-                    '';
-                    example = ["client" "federation"];
-                  };
-                  compress = mkOption {
-                    type = types.bool;
-                    description = ''
-                      Should synapse compress HTTP responses to clients that support it?
-                      This should be disabled if running synapse behind a load balancer
-                      that can do automatic compression.
-                    '';
-                  };
-                };
-              });
-              description = ''
-                List of HTTP resources to serve on this listener.
-              '';
-            };
-          };
-        });
-        default = [{
-          port = 8448;
-          bind_address = "";
-          type = "http";
-          tls = true;
-          x_forwarded = false;
-          resources = [
-            { names = ["client"]; compress = true; }
-            { names = ["federation"]; compress = false; }
-          ];
-        }];
-        description = ''
-          List of ports that Synapse should listen on, their purpose and their configuration.
-        '';
-      };
-      verbose = mkOption {
-        type = types.str;
-        default = "0";
-        description = "Logging verbosity level.";
-      };
-      rc_messages_per_second = mkOption {
-        type = types.str;
-        default = "0.2";
-        description = "Number of messages a client can send per second";
-      };
-      rc_message_burst_count = mkOption {
-        type = types.str;
-        default = "10.0";
-        description = "Number of message a client can send before being throttled";
-      };
-      federation_rc_window_size = mkOption {
-        type = types.str;
-        default = "1000";
-        description = "The federation window size in milliseconds";
-      };
-      federation_rc_sleep_limit = mkOption {
-        type = types.str;
-        default = "10";
-        description = ''
-          The number of federation requests from a single server in a window
-          before the server will delay processing the request.
-        '';
-      };
-      federation_rc_sleep_delay = mkOption {
-        type = types.str;
-        default = "500";
-        description = ''
-          The duration in milliseconds to delay processing events from
-          remote servers by if they go over the sleep limit.
-        '';
-      };
-      federation_rc_reject_limit = mkOption {
-        type = types.str;
-        default = "50";
-        description = ''
-          The maximum number of concurrent federation requests allowed
-          from a single server
-        '';
-      };
-      federation_rc_concurrent = mkOption {
-        type = types.str;
-        default = "3";
-        description = "The number of federation requests to concurrently process from a single server";
-      };
-      database_type = mkOption {
-        type = types.enum [ "sqlite3" "psycopg2" ];
-        default = if versionAtLeast config.system.stateVersion "18.03"
-          then "psycopg2"
-          else "sqlite3";
-        defaultText = literalExpression ''
-          if versionAtLeast config.system.stateVersion "18.03"
-            then "psycopg2"
-            else "sqlite3"
-        '';
-        description = ''
-          The database engine name. Can be sqlite or psycopg2.
-        '';
-      };
-      database_name = mkOption {
-        type = types.str;
-        default = "matrix-synapse";
-        description = "Database name.";
-      };
-      database_user = mkOption {
-        type = types.str;
-        default = "matrix-synapse";
-        description = "Database user name.";
-      };
-      database_args = mkOption {
-        type = types.attrs;
-        default = {
-          sqlite3 = { database = "${cfg.dataDir}/homeserver.db"; };
-          psycopg2 = {
-            user = cfg.database_user;
-            database = cfg.database_name;
-          };
-        }.${cfg.database_type};
-        defaultText = literalDocBook ''
-          <variablelist>
-            <varlistentry>
-              <term>using sqlite3</term>
-              <listitem>
-                <programlisting>
-                  { database = "''${config.${opt.dataDir}}/homeserver.db"; }
-                </programlisting>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>using psycopg2</term>
-              <listitem>
-                <programlisting>
-                  psycopg2 = {
-                    user = config.${opt.database_user};
-                    database = config.${opt.database_name};
-                  }
-                </programlisting>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-        '';
-        description = ''
-          Arguments to pass to the engine.
-        '';
-      };
-      event_cache_size = mkOption {
-        type = types.str;
-        default = "10K";
-        description = "Number of events to cache in memory.";
-      };
-      url_preview_enabled = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Is the preview URL API enabled?  If enabled, you *must* specify an
-          explicit url_preview_ip_range_blacklist of IPs that the spider is
-          denied from accessing.
-        '';
-      };
-      url_preview_ip_range_blacklist = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "127.0.0.0/8"
-          "10.0.0.0/8"
-          "172.16.0.0/12"
-          "192.168.0.0/16"
-          "100.64.0.0/10"
-          "169.254.0.0/16"
-          "::1/128"
-          "fe80::/64"
-          "fc00::/7"
-        ];
-        description = ''
-          List of IP address CIDR ranges that the URL preview spider is denied
-          from accessing.
-        '';
-      };
-      url_preview_ip_range_whitelist = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          List of IP address CIDR ranges that the URL preview spider is allowed
-          to access even if they are specified in
-          url_preview_ip_range_blacklist.
-        '';
-      };
-      url_preview_url_blacklist = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Optional list of URL matches that the URL preview spider is
-          denied from accessing.
-        '';
-      };
-      recaptcha_private_key = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          This Home Server's ReCAPTCHA private key.
-        '';
-      };
-      recaptcha_public_key = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          This Home Server's ReCAPTCHA public key.
-        '';
-      };
-      enable_registration_captcha = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enables ReCaptcha checks when registering, preventing signup
-          unless a captcha is answered. Requires a valid ReCaptcha
-          public/private key.
-        '';
-      };
-      turn_uris = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          The public URIs of the TURN server to give to clients
-        '';
-      };
-      turn_shared_secret = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          The shared secret used to compute passwords for the TURN server
-        '';
-      };
-      turn_user_lifetime = mkOption {
-        type = types.str;
-        default = "1h";
-        description = "How long generated TURN credentials last";
-      };
-      enable_registration = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable registration for new users.
-        '';
-      };
-      registration_shared_secret = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          If set, allows registration by anyone who also has the shared
-          secret, even if registration is otherwise disabled.
-        '';
-      };
-      enable_metrics = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable collection and rendering of performance metrics
-        '';
-      };
-      report_stats = mkOption {
-        type = types.bool;
-        default = false;
-        description = "";
-      };
-      servers = mkOption {
-        type = types.attrsOf (types.attrsOf types.str);
-        default = {
-          "matrix.org" = {
-            "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
-          };
-        };
-        description = ''
-          The trusted servers to download signing keys from.
-        '';
-      };
-      max_upload_size = mkOption {
-        type = types.str;
-        default = "10M";
-        description = "The largest allowed upload size in bytes";
-      };
-      max_image_pixels = mkOption {
-        type = types.str;
-        default = "32M";
-        description = "Maximum number of pixels that will be thumbnailed";
-      };
-      dynamic_thumbnails = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to generate new thumbnails on the fly to precisely match
-          the resolution requested by the client. If true then whenever
-          a new resolution is requested by the client the server will
-          generate a new thumbnail. If false the server will pick a thumbnail
-          from a precalculated list.
-        '';
-      };
-      user_creation_max_duration = mkOption {
-        type = types.str;
-        default = "1209600000";
-        description = ''
-          Sets the expiry for the short term user creation in
-          milliseconds. The default value is two weeks.
-        '';
-      };
-      bcrypt_rounds = mkOption {
-        type = types.str;
-        default = "12";
-        description = ''
-          Set the number of bcrypt rounds used to generate password hash.
-          Larger numbers increase the work factor needed to generate the hash.
-        '';
-      };
-      allow_guest_access = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Allows users to register as guests without a password/email/etc, and
-          participate in rooms hosted on this server which have been made
-          accessible to anonymous users.
-        '';
-      };
-      account_threepid_delegates.email = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Delegate email sending to https://example.org
-        '';
-      };
-      account_threepid_delegates.msisdn = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Delegate SMS sending to this local process (https://localhost:8090)
-        '';
-      };
-      room_prejoin_state.additional_event_types = mkOption {
-        default = [];
-        type = types.listOf types.str;
-        description = ''
-          Additional events to share with users who received an invite.
-        '';
-      };
-      room_prejoin_state.disable_default_event_types = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Whether to disable the default state-event types for users invited to a room.
-          These are:
-
-          <itemizedlist>
-          <listitem><para>m.room.join_rules</para></listitem>
-          <listitem><para>m.room.canonical_alias</para></listitem>
-          <listitem><para>m.room.avatar</para></listitem>
-          <listitem><para>m.room.encryption</para></listitem>
-          <listitem><para>m.room.name</para></listitem>
-          <listitem><para>m.room.create</para></listitem>
-          </itemizedlist>
-        '';
-      };
-      macaroon_secret_key = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Secret key for authentication tokens
-        '';
-      };
-      expire_access_token = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable access token expiration.
-        '';
-      };
-      key_refresh_interval = mkOption {
-        type = types.str;
-        default = "1d";
-        description = ''
-          How long key response published by this server is valid for.
-          Used to set the valid_until_ts in /key/v2 APIs.
-          Determines how quickly servers will query to check which keys
-          are still valid.
-        '';
-      };
-      app_service_config_files = mkOption {
-        type = types.listOf types.path;
-        default = [ ];
-        description = ''
-          A list of application service config file to use
-        '';
-      };
-      redaction_retention_period = mkOption {
-        type = types.int;
-        default = 7;
-        description = ''
-          How long to keep redacted events in unredacted form in the database.
-        '';
-      };
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Extra config options for matrix-synapse.
-        '';
-      };
-      extraConfigFiles = mkOption {
-        type = types.listOf types.path;
-        default = [];
-        description = ''
-          Extra config files to include.
-
-          The configuration files will be included based on the command line
-          argument --config-path. This allows to configure secrets without
-          having to go through the Nix store, e.g. based on deployment keys if
-          NixOPS is in use.
-        '';
-      };
-      logConfig = mkOption {
-        type = types.lines;
-        default = readFile ./matrix-synapse-log_config.yaml;
-        description = ''
-          A yaml python logging config file
-        '';
-      };
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/matrix-synapse";
-        description = ''
-          The directory where matrix-synapse stores its stateful data such as
-          certificates, media and uploads.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = [
-      { assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
-        message = ''
-          Cannot deploy matrix-synapse with a configuration for a local postgresql database
-            and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
-            database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
-            further reference).
-
-            If you
-            - try to deploy a fresh synapse, you need to configure the database yourself. An example
-              for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
-            - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
-              to your configuration.
-
-          For further information about this update, please read the release-notes of 20.03 carefully.
-        '';
-      }
-    ];
-
-    services.matrix-synapse.configFile = "${configFile}";
-
-    users.users.matrix-synapse = {
-      group = "matrix-synapse";
-      home = cfg.dataDir;
-      createHome = true;
-      shell = "${pkgs.bash}/bin/bash";
-      uid = config.ids.uids.matrix-synapse;
-    };
-
-    users.groups.matrix-synapse = {
-      gid = config.ids.gids.matrix-synapse;
-    };
-
-    systemd.services.matrix-synapse = {
-      description = "Synapse Matrix homeserver";
-      after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
-      wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        ${cfg.package}/bin/synapse_homeserver \
-          --config-path ${configFile} \
-          --keys-directory ${cfg.dataDir} \
-          --generate-keys
-      '';
-      environment = {
-        PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
-      } // optionalAttrs (cfg.withJemalloc) {
-        LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
-      };
-      serviceConfig = {
-        Type = "notify";
-        User = "matrix-synapse";
-        Group = "matrix-synapse";
-        WorkingDirectory = cfg.dataDir;
-        ExecStartPre = [ ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
-          chown matrix-synapse:matrix-synapse ${cfg.dataDir}/homeserver.signing.key
-          chmod 0600 ${cfg.dataDir}/homeserver.signing.key
-        '')) ];
-        ExecStart = ''
-          ${cfg.package}/bin/synapse_homeserver \
-            ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
-            --keys-directory ${cfg.dataDir}
-        '';
-        ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
-        Restart = "on-failure";
-        UMask = "0077";
-      };
-    };
-
-    environment.systemPackages = [ registerNewMatrixUser ];
-  };
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
-      The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
-      as the behavior is now obsolete.
-    '')
-    (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
-      Database configuration must be done manually. An exemplary setup is demonstrated in
-      <nixpkgs/nixos/tests/matrix-synapse.nix>
-    '')
-    (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
-    (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
-      You may add additional event types via
-      `services.matrix-synapse.room_prejoin_state.additional_event_types` and
-      disable the default events via
-      `services.matrix-synapse.room_prejoin_state.disable_default_event_types`.
-    '')
-  ];
-
-  meta.doc = ./matrix-synapse.xml;
-  meta.maintainers = teams.matrix.members;
-
-}
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index 6a9eeb02095c..97005c9d914f 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -49,6 +49,13 @@ in
     services.nitter = {
       enable = mkEnableOption "If enabled, start Nitter.";
 
+      package = mkOption {
+        default = pkgs.nitter;
+        type = types.package;
+        defaultText = literalExpression "pkgs.nitter";
+        description = "The nitter derivation to use.";
+      };
+
       server = {
         address = mkOption {
           type =  types.str;
@@ -78,8 +85,8 @@ in
 
         staticDir = mkOption {
           type = types.path;
-          default = "${pkgs.nitter}/share/nitter/public";
-          defaultText = literalExpression ''"''${pkgs.nitter}/share/nitter/public"'';
+          default = "${cfg.package}/share/nitter/public";
+          defaultText = literalExpression ''"''${config.services.nitter.package}/share/nitter/public"'';
           description = "Path to the static files directory.";
         };
 
@@ -306,8 +313,8 @@ in
           Environment = [ "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" ];
           # Some parts of Nitter expect `public` folder in working directory,
           # see https://github.com/zedeus/nitter/issues/414
-          WorkingDirectory = "${pkgs.nitter}/share/nitter";
-          ExecStart = "${pkgs.nitter}/bin/nitter";
+          WorkingDirectory = "${cfg.package}/share/nitter";
+          ExecStart = "${cfg.package}/bin/nitter";
           ExecStartPre = "${preStart}";
           AmbientCapabilities = lib.mkIf (cfg.server.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
           Restart = "on-failure";
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index ca59ea293783..2b21df91b82f 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -730,6 +730,40 @@ in
           };
 
         restartTriggers = [ nixConf ];
+
+        # `stopIfChanged = false` changes to switch behavior
+        # from   stop -> update units -> start
+        #   to   update units -> restart
+        #
+        # The `stopIfChanged` setting therefore controls a trade-off between a
+        # more predictable lifecycle, which runs the correct "version" of
+        # the `ExecStop` line, and on the other hand the availability of
+        # sockets during the switch, as the effectiveness of the stop operation
+        # depends on the socket being stopped as well.
+        #
+        # As `nix-daemon.service` does not make use of `ExecStop`, we prefer
+        # to keep the socket up and available. This is important for machines
+        # that run Nix-based services, such as automated build, test, and deploy
+        # services, that expect the daemon socket to be available at all times.
+        #
+        # Notably, the Nix client does not retry on failure to connect to the
+        # daemon socket, and the in-process RemoteStore instance will disable
+        # itself. This makes retries infeasible even for services that are
+        # aware of the issue. Failure to connect can affect not only new client
+        # processes, but also new RemoteStore instances in existing processes,
+        # as well as existing RemoteStore instances that have not saturated
+        # their connection pool.
+        #
+        # Also note that `stopIfChanged = true` does not kill existing
+        # connection handling daemons, as one might wish to happen before a
+        # breaking Nix upgrade (which is rare). The daemon forks that handle
+        # the individual connections split off into their own sessions, causing
+        # them not to be stopped by systemd.
+        # If a Nix upgrade does require all existing daemon processes to stop,
+        # nix-daemon must do so on its own accord, and only when the new version
+        # starts and detects that Nix's persistent state needs an upgrade.
+        stopIfChanged = false;
+
       };
 
     # Set up the environment variables for running Nix.
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index b311b91b4a03..17da020bf3e2 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -259,7 +259,7 @@ in
         ipfs --offline config Mounts.IPFS ${cfg.ipfsMountDir}
         ipfs --offline config Mounts.IPNS ${cfg.ipnsMountDir}
       '' + optionalString cfg.autoMigrate ''
-        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -y
+        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
       '' + ''
         ipfs --offline config show \
           | ${pkgs.jq}/bin/jq '. * $extraConfig' --argjson extraConfig ${
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index fc06cdaa6e58..3049c4f2bce9 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -3,103 +3,101 @@
 let
   inherit (lib) mkEnableOption mkIf mkOption optionalString types;
 
-  generic = variant:
-    let
-      cfg = config.services.${variant};
-      pkg = pkgs.${variant};
-      birdBin = if variant == "bird6" then "bird6" else "bird";
-      birdc = if variant == "bird6" then "birdc6" else "birdc";
-      descr =
-        { bird = "1.6.x with IPv4 support";
-          bird6 = "1.6.x with IPv6 support";
-          bird2 = "2.x";
-        }.${variant};
-    in {
-      ###### interface
-      options = {
-        services.${variant} = {
-          enable = mkEnableOption "BIRD Internet Routing Daemon (${descr})";
-          config = mkOption {
-            type = types.lines;
-            description = ''
-              BIRD Internet Routing Daemon configuration file.
-              <link xlink:href='http://bird.network.cz/'/>
-            '';
-          };
-          checkConfig = mkOption {
-            type = types.bool;
-            default = true;
-            description = ''
-              Whether the config should be checked at build time.
-              When the config can't be checked during build time, for example when it includes
-              other files, either disable this option or use <code>preCheckConfig</code> to create
-              the included files before checking.
-            '';
-          };
-          preCheckConfig = mkOption {
-            type = types.lines;
-            default = "";
-            example = ''
-              echo "cost 100;" > include.conf
-            '';
-            description = ''
-              Commands to execute before the config file check. The file to be checked will be
-              available as <code>${variant}.conf</code> in the current directory.
+  cfg = config.services.bird2;
+  caps = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ];
+in
+{
+  ###### interface
+  options = {
+    services.bird2 = {
+      enable = mkEnableOption "BIRD Internet Routing Daemon";
+      config = mkOption {
+        type = types.lines;
+        description = ''
+          BIRD Internet Routing Daemon configuration file.
+          <link xlink:href='http://bird.network.cz/'/>
+        '';
+      };
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether the config should be checked at build time.
+          When the config can't be checked during build time, for example when it includes
+          other files, either disable this option or use <code>preCheckConfig</code> to create
+          the included files before checking.
+        '';
+      };
+      preCheckConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          echo "cost 100;" > include.conf
+        '';
+        description = ''
+          Commands to execute before the config file check. The file to be checked will be
+          available as <code>bird2.conf</code> in the current directory.
 
-              Files created with this option will not be available at service runtime, only during
-              build time checking.
-            '';
-          };
-        };
+          Files created with this option will not be available at service runtime, only during
+          build time checking.
+        '';
       };
+    };
+  };
 
-      ###### implementation
-      config = mkIf cfg.enable {
-        environment.systemPackages = [ pkg ];
 
-        environment.etc."bird/${variant}.conf".source = pkgs.writeTextFile {
-          name = "${variant}.conf";
-          text = cfg.config;
-          checkPhase = optionalString cfg.checkConfig ''
-            ln -s $out ${variant}.conf
-            ${cfg.preCheckConfig}
-            ${pkg}/bin/${birdBin} -d -p -c ${variant}.conf
-          '';
-        };
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "bird" ] "Use services.bird2 instead")
+    (lib.mkRemovedOptionModule [ "services" "bird6" ] "Use services.bird2 instead")
+  ];
 
-        systemd.services.${variant} = {
-          description = "BIRD Internet Routing Daemon (${descr})";
-          wantedBy = [ "multi-user.target" ];
-          reloadIfChanged = true;
-          restartTriggers = [ config.environment.etc."bird/${variant}.conf".source ];
-          serviceConfig = {
-            Type = "forking";
-            Restart = "on-failure";
-            ExecStart = "${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -u ${variant} -g ${variant}";
-            ExecReload = "/bin/sh -c '${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -p && ${pkg}/bin/${birdc} configure'";
-            ExecStop = "${pkg}/bin/${birdc} down";
-            CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_SETUID" "CAP_SETGID"
-                                      # see bird/sysdep/linux/syspriv.h
-                                      "CAP_NET_BIND_SERVICE" "CAP_NET_BROADCAST" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
-            ProtectSystem = "full";
-            ProtectHome = "yes";
-            SystemCallFilter="~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
-            MemoryDenyWriteExecute = "yes";
-          };
-        };
-        users = {
-          users.${variant} = {
-            description = "BIRD Internet Routing Daemon user";
-            group = variant;
-            isSystemUser = true;
-          };
-          groups.${variant} = {};
-        };
-      };
-    };
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.bird ];
 
-in
+    environment.etc."bird/bird2.conf".source = pkgs.writeTextFile {
+      name = "bird2";
+      text = cfg.config;
+      checkPhase = optionalString cfg.checkConfig ''
+        ln -s $out bird2.conf
+        ${cfg.preCheckConfig}
+        ${pkgs.bird}/bin/bird -d -p -c bird2.conf
+      '';
+    };
 
-{
-  imports = map generic [ "bird" "bird6" "bird2" ];
+    systemd.services.bird2 = {
+      description = "BIRD Internet Routing Daemon";
+      wantedBy = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      restartTriggers = [ config.environment.etc."bird/bird2.conf".source ];
+      serviceConfig = {
+        Type = "forking";
+        Restart = "on-failure";
+        User = "bird2";
+        Group = "bird2";
+        ExecStart = "${pkgs.bird}/bin/bird -c /etc/bird/bird2.conf";
+        ExecReload = "${pkgs.bird}/bin/birdc configure";
+        ExecStop = "${pkgs.bird}/bin/birdc down";
+        RuntimeDirectory = "bird";
+        CapabilityBoundingSet = caps;
+        AmbientCapabilities = caps;
+        ProtectSystem = "full";
+        ProtectHome = "yes";
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
+        MemoryDenyWriteExecute = "yes";
+      };
+    };
+    users = {
+      users.bird2 = {
+        description = "BIRD Internet Routing Daemon user";
+        group = "bird2";
+        isSystemUser = true;
+      };
+      groups.bird2 = { };
+    };
+  };
 }
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 992678c43ebe..06ec04dbbf16 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -306,7 +306,7 @@ in
         Type = if forking then "forking" else "simple";
         PIDFile = mkIf forking "/run/murmur/murmurd.pid";
         EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
-        ExecStart = "${cfg.package}/bin/murmurd -ini /run/murmur/murmurd.ini";
+        ExecStart = "${cfg.package}/bin/mumble-server -ini /run/murmur/murmurd.ini";
         Restart = "always";
         RuntimeDirectory = "murmur";
         RuntimeDirectoryMode = "0700";
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index a9801036b00c..7a9d9e5428a7 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -556,6 +556,7 @@ in {
 
     boot.kernelModules = [ "ctr" ];
 
+    security.polkit.enable = true;
     security.polkit.extraConfig = polkitConf;
 
     services.dbus.packages = cfg.packages
diff --git a/nixos/modules/services/networking/snowflake-proxy.nix b/nixos/modules/services/networking/snowflake-proxy.nix
new file mode 100644
index 000000000000..2124644ed9b5
--- /dev/null
+++ b/nixos/modules/services/networking/snowflake-proxy.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.snowflake-proxy;
+in
+{
+  options = {
+    services.snowflake-proxy = {
+      enable = mkEnableOption "System to defeat internet censorship";
+
+      broker = mkOption {
+        description = "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
+        type = with types; nullOr str;
+        default = null;
+      };
+
+      capacity = mkOption {
+        description = "Limits the amount of maximum concurrent clients allowed.";
+        type = with types; nullOr int;
+        default = null;
+      };
+
+      relay = mkOption {
+        description = "websocket relay URL (default \"wss://snowflake.bamsoftware.com/\")";
+        type = with types; nullOr str;
+        default = null;
+      };
+
+      stun = mkOption {
+        description = "STUN broker URL (default \"stun:stun.stunprotocol.org:3478\")";
+        type = with types; nullOr str;
+        default = null;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.snowflake-proxy = {
+      wantedBy = [ "network-online.target" ];
+      serviceConfig = {
+        ExecStart =
+          "${pkgs.snowflake}/bin/proxy " + concatStringsSep " " (
+            optional (cfg.broker != null) "-broker ${cfg.broker}"
+            ++ optional (cfg.capacity != null) "-capacity ${builtins.toString cfg.capacity}"
+            ++ optional (cfg.relay != null) "-relay ${cfg.relay}"
+            ++ optional (cfg.stun != null) "-stun ${cfg.stun}"
+          );
+
+        # Security Hardening
+        # Refer to systemd.exec(5) for option descriptions.
+        CapabilityBoundingSet = "";
+
+        # implies RemoveIPC=, PrivateTmp=, NoNewPrivileges=, RestrictSUIDSGID=,
+        # ProtectSystem=strict, ProtectHome=read-only
+        DynamicUser = true;
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @mount @obsolete @reboot @swap @privileged @resources";
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ yayayayaka ];
+}
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 340cbbf02fb4..95a0ad8770e2 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -9,7 +9,7 @@ let
   pkg = pkgs.clamav;
 
   toKeyValue = generators.toKeyValue {
-    mkKeyValue = generators.mkKeyValueDefault {} " ";
+    mkKeyValue = generators.mkKeyValueDefault { } " ";
     listsAsDuplicateKeys = true;
   };
 
@@ -30,7 +30,7 @@ in
 
         settings = mkOption {
           type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
-          default = {};
+          default = { };
           description = ''
             ClamAV configuration. Refer to <link xlink:href="https://linux.die.net/man/5/clamd.conf"/>,
             for details on supported values.
@@ -59,7 +59,7 @@ in
 
         settings = mkOption {
           type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
-          default = {};
+          default = { };
           description = ''
             freshclam configuration. Refer to <link xlink:href="https://linux.die.net/man/5/freshclam.conf"/>,
             for details on supported values.
@@ -104,7 +104,6 @@ in
     systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
       description = "ClamAV daemon (clamd)";
       after = optional cfg.updater.enable "clamav-freshclam.service";
-      requires = optional cfg.updater.enable "clamav-freshclam.service";
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ clamdConfigFile ];
 
@@ -134,7 +133,7 @@ in
     systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
       description = "ClamAV virus database updater (freshclam)";
       restartTriggers = [ freshclamConfigFile ];
-
+      after = [ "network-online.target" ];
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
         chown ${clamavUser}:${clamavGroup} ${stateDir}
diff --git a/nixos/modules/services/security/fprot.nix b/nixos/modules/services/security/fprot.nix
deleted file mode 100644
index df60d553e85b..000000000000
--- a/nixos/modules/services/security/fprot.nix
+++ /dev/null
@@ -1,82 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  fprotUser = "fprot";
-  stateDir = "/var/lib/fprot";
-  fprotGroup = fprotUser;
-  cfg = config.services.fprot;
-in {
-  options = {
-
-    services.fprot = {
-      updater = {
-        enable = mkEnableOption "automatic F-Prot virus definitions database updates";
-
-        productData = mkOption {
-          description = ''
-            product.data file. Defaults to the one supplied with installation package.
-          '';
-          type = types.path;
-        };
-
-        frequency = mkOption {
-          default = 30;
-          type = types.int;
-          description = ''
-            Update virus definitions every X minutes.
-          '';
-        };
-
-        licenseKeyfile = mkOption {
-          type = types.path;
-          description = ''
-            License keyfile. Defaults to the one supplied with installation package.
-          '';
-        };
-
-      };
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.updater.enable {
-
-    services.fprot.updater.productData = mkDefault "${pkgs.fprot}/opt/f-prot/product.data";
-    services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key";
-
-    environment.systemPackages = [ pkgs.fprot ];
-    environment.etc."f-prot.conf" = {
-      source = "${pkgs.fprot}/opt/f-prot/f-prot.conf";
-    };
-
-    users.users.${fprotUser} =
-      { uid = config.ids.uids.fprot;
-        description = "F-Prot daemon user";
-        home = stateDir;
-      };
-
-    users.groups.${fprotGroup} =
-      { gid = config.ids.gids.fprot; };
-
-    services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ];
-
-    systemd.services.fprot-updater = {
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = false;
-      };
-      wantedBy = [ "multi-user.target" ];
-
-      # have to copy fpupdate executable because it insists on storing the virus database in the same dir
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${fprotUser}:${fprotGroup} ${stateDir}
-        cp ${pkgs.fprot}/opt/f-prot/fpupdate ${stateDir}
-        ln -sf ${cfg.updater.productData} ${stateDir}/product.data
-      '';
-
-      script = "/var/lib/fprot/fpupdate --keyfile ${cfg.updater.licenseKeyfile}";
-    };
- };
-}
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
index 919346cf2bb1..f9b4985e1991 100644
--- a/nixos/modules/services/security/opensnitch.nix
+++ b/nixos/modules/services/security/opensnitch.nix
@@ -3,22 +3,123 @@
 with lib;
 
 let
-  name = "opensnitch";
   cfg = config.services.opensnitch;
+  format = pkgs.formats.json {};
 in {
   options = {
     services.opensnitch = {
       enable = mkEnableOption "Opensnitch application firewall";
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = format.type;
+
+          options = {
+            Server = {
+
+              Address = mkOption {
+                type = types.str;
+                description = ''
+                  Unix socket path (unix:///tmp/osui.sock, the "unix:///" part is
+                  mandatory) or TCP socket (192.168.1.100:50051).
+                '';
+              };
+
+              LogFile = mkOption {
+                type = types.path;
+                description = ''
+                  File to write logs to (use /dev/stdout to write logs to standard
+                  output).
+                '';
+              };
+
+            };
+
+            DefaultAction = mkOption {
+              type = types.enum [ "allow" "deny" ];
+              description = ''
+                Default action whether to block or allow application internet
+                access.
+              '';
+            };
+
+            DefaultDuration = mkOption {
+              type = types.enum [
+                "once" "always" "until restart" "30s" "5m" "15m" "30m" "1h"
+              ];
+              description = ''
+                Default duration of firewall rule.
+              '';
+            };
+
+            InterceptUnknown = mkOption {
+              type = types.bool;
+              description = ''
+                Wheter to intercept spare connections.
+              '';
+            };
+
+            ProcMonitorMethod = mkOption {
+              type = types.enum [ "ebpf" "proc" "ftrace" "audit" ];
+              description = ''
+                Which process monitoring method to use.
+              '';
+            };
+
+            LogLevel = mkOption {
+              type = types.enum [ 0 1 2 3 4 ];
+              description = ''
+                Default log level from 0 to 4 (debug, info, important, warning,
+                error).
+              '';
+            };
+
+            Firewall = mkOption {
+              type = types.enum [ "iptables" "nftables" ];
+              description = ''
+                Which firewall backend to use.
+              '';
+            };
+
+            Stats = {
+
+              MaxEvents = mkOption {
+                type = types.int;
+                description = ''
+                  Max events to send to the GUI.
+                '';
+              };
+
+              MaxStats = mkOption {
+                type = types.int;
+                description = ''
+                  Max stats per item to keep in backlog.
+                '';
+              };
+
+            };
+          };
+        };
+        description = ''
+          opensnitchd configuration. Refer to
+          <link xlink:href="https://github.com/evilsocket/opensnitch/wiki/Configurations"/>
+          for details on supported values.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable {
 
+    # pkg.opensnitch is referred to elsewhere in the module so we don't need to worry about it being garbage collected
+    services.opensnitch.settings = mapAttrs (_: v: mkDefault v) (builtins.fromJSON (builtins.unsafeDiscardStringContext (builtins.readFile "${pkgs.opensnitch}/etc/default-config.json")));
+
     systemd = {
       packages = [ pkgs.opensnitch ];
       services.opensnitchd.wantedBy = [ "multi-user.target" ];
     };
 
+    environment.etc."opensnitchd/default-config.json".source = format.generate "default-config.json" cfg.settings;
+
   };
 }
 
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index 452efc736439..b355df056bc1 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -39,20 +39,12 @@ in
         '';
       };
 
-      useKernelOOMKiller= mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Use kernel OOM killer instead of own user-space implementation.
-        '';
-      };
-
+      # TODO: remove or warn after 1.7 (https://github.com/rfjakob/earlyoom/commit/7ebc4554)
       ignoreOOMScoreAdjust = mkOption {
         type = types.bool;
         default = false;
         description = ''
           Ignore oom_score_adjust values of processes.
-          User-space implementation only.
         '';
       };
 
@@ -87,16 +79,21 @@ in
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "earlyoom" "useKernelOOMKiller" ] ''
+      This option is deprecated and ignored by earlyoom since 1.2.
+    '')
+  ];
+
   config = mkIf ecfg.enable {
     assertions = [
       { assertion = ecfg.freeMemThreshold > 0 && ecfg.freeMemThreshold <= 100;
         message = "Needs to be a positive percentage"; }
       { assertion = ecfg.freeSwapThreshold > 0 && ecfg.freeSwapThreshold <= 100;
         message = "Needs to be a positive percentage"; }
-      { assertion = !ecfg.useKernelOOMKiller || !ecfg.ignoreOOMScoreAdjust;
-        message = "Both options in conjunction do not make sense"; }
     ];
 
+    # TODO: reimplement this option as -N after 1.7 (https://github.com/rfjakob/earlyoom/commit/afe03606)
     warnings = optional (ecfg.notificationsCommand != null)
       "`services.earlyoom.notificationsCommand` is deprecated and ignored by earlyoom since 1.6.";
 
@@ -107,15 +104,13 @@ in
       serviceConfig = {
         StandardOutput = "null";
         StandardError = "journal";
-        ExecStart = ''
-          ${pkgs.earlyoom}/bin/earlyoom \
-          -m ${toString ecfg.freeMemThreshold} \
-          -s ${toString ecfg.freeSwapThreshold} \
-          ${optionalString ecfg.useKernelOOMKiller "-k"} \
-          ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
-          ${optionalString ecfg.enableDebugInfo "-d"} \
-          ${optionalString ecfg.enableNotifications "-n"}
-        '';
+        ExecStart = concatStringsSep " " ([
+          "${pkgs.earlyoom}/bin/earlyoom"
+          "-m ${toString ecfg.freeMemThreshold}"
+          "-s ${toString ecfg.freeSwapThreshold}"
+        ] ++ optional ecfg.ignoreOOMScoreAdjust "-i"
+          ++ optional ecfg.enableDebugInfo "-d"
+          ++ optional ecfg.enableNotifications "-n");
       };
     };
 
diff --git a/nixos/modules/services/wayland/cage.nix b/nixos/modules/services/wayland/cage.nix
index d2bbc4fc057b..a32b81a916fc 100644
--- a/nixos/modules/services/wayland/cage.nix
+++ b/nixos/modules/services/wayland/cage.nix
@@ -81,6 +81,8 @@ in {
       };
     };
 
+    security.polkit.enable = true;
+
     security.pam.services.cage.text = ''
       auth    required pam_unix.so nullok
       account required pam_unix.so
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index a01f0049b2c7..22c16be76139 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -693,6 +693,7 @@ in
             RemainAfterExit = true;
             User = "postgres";
             Group = "postgres";
+            LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
           };
           script = ''
             set -o errexit -o pipefail -o nounset -o errtrace
@@ -701,7 +702,8 @@ in
             create_role="$(mktemp)"
             trap 'rm -f "$create_role"' ERR EXIT
 
-            echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$(<'${cfg.database.passwordFile}')' CREATEDB" > "$create_role"
+            db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
+            echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role"
             psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
             psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
           '';
@@ -717,14 +719,14 @@ in
             RemainAfterExit = true;
             User = config.services.mysql.user;
             Group = config.services.mysql.group;
+            LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
           };
           script = ''
             set -o errexit -o pipefail -o nounset -o errtrace
             shopt -s inherit_errexit
-
-            db_password="$(<'${cfg.database.passwordFile}')"
+            db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
             ( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
-              echo "CREATE DATABASE keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
+              echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
               echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
             ) | mysql -N
           '';
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index a65428018260..e195e6e6e824 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -320,6 +320,7 @@ in {
         };
         storage = {
           tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
+          bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
           avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
           videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
           streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
@@ -333,6 +334,15 @@ in {
           plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
           client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
         };
+        import = {
+          videos = {
+            http = {
+              youtube_dl_release = {
+                python_path = "${pkgs.python3}/bin/python";
+              };
+            };
+          };
+        };
       }
       (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/redis.sock"; }; })
     ];
@@ -380,7 +390,7 @@ in {
 
       environment = env;
 
-      path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn youtube-dl ];
+      path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn python3 ];
 
       script = ''
         #!/bin/sh
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 84b75c83aeab..27dfed3cc14c 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -267,6 +267,8 @@ in
     # Enable the accounts daemon to find lightdm's dbus interface
     environment.systemPackages = [ lightdm ];
 
+    security.polkit.enable = true;
+
     security.pam.services.lightdm.text = ''
         auth      substack      login
         account   include       login
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index ec6d86d59bdf..0c50d82b23be 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -620,9 +620,6 @@ in
       in optional (driver != null) ({ inherit name; modules = []; driverName = name; display = true; } // driver));
 
     assertions = [
-      { assertion = config.security.polkit.enable;
-        message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
-      }
       (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
         assertion = length primaryHeads < 2;
         message = "Only one head is allowed to be primary in "
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index a8fe14c58f05..3a5ffe822ed7 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -307,6 +307,7 @@ sub handleModifiedUnit {
         # seem to get applied on daemon-reload.
     } elsif ($unit =~ /\.mount$/) {
         # Reload the changed mount unit to force a remount.
+        # FIXME: only reload when Options= changed, restart otherwise
         $unitsToReload->{$unit} = 1;
         recordUnit($reloadListFile, $unit);
     } elsif ($unit =~ /\.socket$/) {
@@ -339,7 +340,7 @@ sub handleModifiedUnit {
                 # If this unit is socket-activated, then stop the
                 # socket unit(s) as well, and restart the
                 # socket(s) instead of the service.
-                my $socketActivated = 0;
+                my $socket_activated = 0;
                 if ($unit =~ /\.service$/) {
                     my @sockets = split(/ /, join(" ", @{$unitInfo{Service}{Sockets} // []}));
                     if (scalar @sockets == 0) {
@@ -347,13 +348,15 @@ sub handleModifiedUnit {
                     }
                     foreach my $socket (@sockets) {
                         if (defined $activePrev->{$socket}) {
+                            # We can now be sure this is a socket-activate unit
+
                             $unitsToStop->{$socket} = 1;
                             # Only restart sockets that actually
                             # exist in new configuration:
                             if (-e "$out/etc/systemd/system/$socket") {
                                 $unitsToStart->{$socket} = 1;
                                 recordUnit($startListFile, $socket);
-                                $socketActivated = 1;
+                                $socket_activated = 1;
                             }
                             # Remove from units to reload so we don't restart and reload
                             if ($unitsToReload->{$unit}) {
@@ -368,7 +371,7 @@ sub handleModifiedUnit {
                 # that this unit needs to be started below.
                 # We write this to a file to ensure that the
                 # service gets restarted if we're interrupted.
-                if (!$socketActivated) {
+                if (!$socket_activated) {
                     $unitsToStart->{$unit} = 1;
                     recordUnit($startListFile, $unit);
                 }
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 441faa03af00..4019af63ad35 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -1224,6 +1224,7 @@ in
         keep = 1;
         extraConfig = ''
           create 0660 root ${config.users.groups.utmp.name}
+          minsize 1M
         '';
       };
       "/var/log/wtmp" = mapAttrs (_: mkDefault) {
@@ -1231,6 +1232,7 @@ in
         keep = 1;
         extraConfig = ''
           create 0664 root ${config.users.groups.utmp.name}
+          minsize 1M
         '';
       };
     };
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index bd7077ff7ea1..9a56b6950155 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -37,8 +37,13 @@ in
       { assertion = cfg.efi -> cfg.hvm;
         message = "EC2 instances using EFI must be HVM instances.";
       }
+      { assertion = versionOlder config.boot.kernelPackages.kernel.version "5.15";
+        message = "ENA driver fails to build with kernel >= 5.15";
+      }
     ];
 
+    boot.kernelPackages = pkgs.linuxKernel.packages.linux_5_10;
+
     boot.growPartition = cfg.hvm;
 
     fileSystems."/" = mkIf (!cfg.zfs.enable) {
diff --git a/nixos/modules/virtualisation/proxmox-lxc.nix b/nixos/modules/virtualisation/proxmox-lxc.nix
new file mode 100644
index 000000000000..3913b474afbe
--- /dev/null
+++ b/nixos/modules/virtualisation/proxmox-lxc.nix
@@ -0,0 +1,64 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options.proxmoxLXC = {
+    privileged = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable privileged mounts
+      '';
+    };
+    manageNetwork = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to manage network interfaces through nix options
+        When false, systemd-networkd is enabled to accept network
+        configuration from proxmox.
+      '';
+    };
+  };
+
+  config =
+    let
+      cfg = config.proxmoxLXC;
+    in
+    {
+      system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
+        storeContents = [{
+          object = config.system.build.toplevel;
+          symlink = "none";
+        }];
+
+        contents = [{
+          source = config.system.build.toplevel + "/init";
+          target = "/sbin/init";
+        }];
+
+        extraCommands = "mkdir -p root etc/systemd/network";
+      };
+
+      boot = {
+        isContainer = true;
+        loader.initScript.enable = true;
+      };
+
+      networking = mkIf (!cfg.manageNetwork) {
+        useDHCP = false;
+        useHostResolvConf = false;
+        useNetworkd = true;
+      };
+
+      services.openssh = {
+        enable = mkDefault true;
+        startWhenNeeded = mkDefault true;
+      };
+
+      systemd.mounts = mkIf (!cfg.privileged)
+        [{ where = "/sys/kernel/debug"; enable = false; }];
+
+    };
+}
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index ee3f3d19174e..fd8a39cfb92b 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -98,7 +98,6 @@ in rec {
         (onFullSupported "nixos.tests.login")
         (onFullSupported "nixos.tests.misc")
         (onFullSupported "nixos.tests.mutableUsers")
-        (onFullSupported "nixos.tests.nano")
         (onFullSupported "nixos.tests.nat.firewall-conntrack")
         (onFullSupported "nixos.tests.nat.firewall")
         (onFullSupported "nixos.tests.nat.standalone")
@@ -130,7 +129,8 @@ in rec {
         (onFullSupported "nixos.tests.networking.networkd.virtual")
         (onFullSupported "nixos.tests.networking.networkd.vlan")
         (onFullSupported "nixos.tests.systemd-networkd-ipv6-prefix-delegation")
-        (onFullSupported "nixos.tests.nfs3.simple")
+        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
+        #(onFullSupported "nixos.tests.nfs3.simple")
         (onFullSupported "nixos.tests.nfs4.simple")
         (onFullSupported "nixos.tests.openssh")
         (onFullSupported "nixos.tests.pantheon")
diff --git a/nixos/release-small.nix b/nixos/release-small.nix
index 996db54c9a40..1d51b4e7f28f 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -38,7 +38,8 @@ in rec {
         login
         misc
         nat
-        nfs3
+        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
+        #nfs3
         openssh
         php
         predictable-interface-names
@@ -107,7 +108,8 @@ in rec {
         "nixos.tests.nat.firewall-conntrack.x86_64-linux"
         "nixos.tests.nat.firewall.x86_64-linux"
         "nixos.tests.nat.standalone.x86_64-linux"
-        "nixos.tests.nfs3.simple.x86_64-linux"
+        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
+        #"nixos.tests.nfs3.simple.x86_64-linux"
         "nixos.tests.openssh.x86_64-linux"
         "nixos.tests.php.fpm.x86_64-linux"
         "nixos.tests.php.pcre.x86_64-linux"
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index c9c39e792514..15b54cd9fe1d 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -189,9 +189,9 @@ in
   grocy = handleTest ./grocy.nix {};
   grub = handleTest ./grub.nix {};
   gvisor = handleTest ./gvisor.nix {};
-  hadoop.all = handleTestOn [ "x86_64-linux" ] ./hadoop/hadoop.nix {};
-  hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
-  hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
+  hadoop.all = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop/hadoop.nix {};
+  hadoop.hdfs = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop/hdfs.nix {};
+  hadoop.yarn = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop/yarn.nix {};
   haka = handleTest ./haka.nix {};
   haproxy = handleTest ./haproxy.nix {};
   hardened = handleTest ./hardened.nix {};
@@ -224,6 +224,7 @@ in
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   initrd-secrets = handleTest ./initrd-secrets.nix {};
+  input-remapper = handleTest ./input-remapper.nix {};
   inspircd = handleTest ./inspircd.nix {};
   installer = handleTest ./installer.nix {};
   invoiceplane = handleTest ./invoiceplane.nix {};
@@ -322,7 +323,6 @@ in
   mysql-replication = handleTest ./mysql/mysql-replication.nix {};
   n8n = handleTest ./n8n.nix {};
   nagios = handleTest ./nagios.nix {};
-  nano = handleTest ./nano.nix {};
   nar-serve = handleTest ./nar-serve.nix {};
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
@@ -394,6 +394,8 @@ in
   pdns-recursor = handleTest ./pdns-recursor.nix {};
   peerflix = handleTest ./peerflix.nix {};
   peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
+  pgadmin4 = handleTest ./pgadmin4.nix {};
+  pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {};
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
   php = handleTest ./php {};
@@ -553,6 +555,7 @@ in
   vikunja = handleTest ./vikunja.nix {};
   virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
   vscodium = discoverTests (import ./vscodium.nix);
+  vsftpd = handleTest ./vsftpd.nix {};
   wasabibackend = handleTest ./wasabibackend.nix {};
   wiki-js = handleTest ./wiki-js.nix {};
   wine = handleTest ./wine.nix {};
@@ -571,6 +574,7 @@ in
   xxh = handleTest ./xxh.nix {};
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
+  zammad = handleTest ./zammad.nix {};
   zfs = handleTest ./zfs.nix {};
   zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
diff --git a/nixos/tests/bird.nix b/nixos/tests/bird.nix
index 50d397be14ee..822a7caea9ba 100644
--- a/nixos/tests/bird.nix
+++ b/nixos/tests/bird.nix
@@ -9,7 +9,7 @@ let
   inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
   inherit (pkgs.lib) optionalString;
 
-  hostShared = hostId: { pkgs, ... }: {
+  makeBird2Host = hostId: { pkgs, ... }: {
     virtualisation.vlans = [ 1 ];
 
     environment.systemPackages = with pkgs; [ jq ];
@@ -24,105 +24,6 @@ let
       name = "eth1";
       networkConfig.Address = "10.0.0.${hostId}/24";
     };
-  };
-
-  birdTest = v4:
-    let variant = "bird${optionalString (!v4) "6"}"; in
-    makeTest {
-      name = variant;
-
-      nodes.host1 = makeBirdHost variant "1";
-      nodes.host2 = makeBirdHost variant "2";
-
-      testScript = makeTestScript variant v4 (!v4);
-    };
-
-  bird2Test = makeTest {
-    name = "bird2";
-
-    nodes.host1 = makeBird2Host "1";
-    nodes.host2 = makeBird2Host "2";
-
-    testScript = makeTestScript "bird2" true true;
-  };
-
-  makeTestScript = variant: v4: v6: ''
-    start_all()
-
-    host1.wait_for_unit("${variant}.service")
-    host2.wait_for_unit("${variant}.service")
-
-    ${optionalString v4 ''
-    with subtest("Waiting for advertised IPv4 routes"):
-      host1.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.2\")) | any'")
-      host2.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.1\")) | any'")
-    ''}
-    ${optionalString v6 ''
-    with subtest("Waiting for advertised IPv6 routes"):
-      host1.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::2\")) | any'")
-      host2.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::1\")) | any'")
-    ''}
-
-    with subtest("Check fake routes in preCheckConfig do not exists"):
-      ${optionalString v4 ''host1.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")''}
-      ${optionalString v4 ''host2.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")''}
-
-      ${optionalString v6 ''host1.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")''}
-      ${optionalString v6 ''host2.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")''}
-  '';
-
-  makeBirdHost = variant: hostId: { pkgs, ... }: {
-    imports = [ (hostShared hostId) ];
-
-    services.${variant} = {
-      enable = true;
-
-      config = ''
-        log syslog all;
-
-        debug protocols all;
-
-        router id 10.0.0.${hostId};
-
-        protocol device {
-        }
-
-        protocol kernel {
-          import none;
-          export all;
-        }
-
-        protocol static {
-          include "static.conf";
-        }
-
-        protocol ospf {
-          export all;
-          area 0 {
-            interface "eth1" {
-              hello 5;
-              wait 5;
-            };
-          };
-        }
-      '';
-
-      preCheckConfig =
-        let
-          route = { bird = "1.2.3.4/32"; bird6 = "fd00::/128"; }.${variant};
-        in
-        ''echo "route ${route} blackhole;" > static.conf'';
-    };
-
-    systemd.tmpfiles.rules =
-      let
-        route = { bird = "10.10.0.${hostId}/32"; bird6 = "fdff::${hostId}/128"; }.${variant};
-      in
-      [ "f /etc/bird/static.conf - - - - route ${route} blackhole;" ];
-  };
-
-  makeBird2Host = hostId: { pkgs, ... }: {
-    imports = [ (hostShared hostId) ];
 
     services.bird2 = {
       enable = true;
@@ -198,8 +99,31 @@ let
     ];
   };
 in
-{
-  bird = birdTest true;
-  bird6 = birdTest false;
-  bird2 = bird2Test;
+makeTest {
+  name = "bird2";
+
+  nodes.host1 = makeBird2Host "1";
+  nodes.host2 = makeBird2Host "2";
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("bird2.service")
+    host2.wait_for_unit("bird2.service")
+    host1.succeed("systemctl reload bird2.service")
+
+    with subtest("Waiting for advertised IPv4 routes"):
+      host1.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.2\")) | any'")
+      host2.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.1\")) | any'")
+    with subtest("Waiting for advertised IPv6 routes"):
+      host1.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::2\")) | any'")
+      host2.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::1\")) | any'")
+
+    with subtest("Check fake routes in preCheckConfig do not exists"):
+      host1.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
+      host2.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
+
+      host1.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
+      host2.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
+  '';
 }
diff --git a/nixos/tests/cntr.nix b/nixos/tests/cntr.nix
index 668470756209..e4e13545b876 100644
--- a/nixos/tests/cntr.nix
+++ b/nixos/tests/cntr.nix
@@ -28,10 +28,16 @@ let
       testScript = ''
         start_all()
         ${backend}.wait_for_unit("${backend}-nginx.service")
-        result = ${backend}.wait_until_succeeds(
-            "cntr attach -t ${backend} nginx sh -- -c 'curl localhost | grep Hello'"
+        ${backend}.wait_for_open_port(8181)
+        # For some reason, the cntr command hangs when run without the &.
+        # As such, we have to do some messy things to ensure we check the exitcode and output in a race-condition-safe manner
+        ${backend}.execute(
+            "(cntr attach -t ${backend} nginx sh -- -c 'curl localhost | grep Hello' > /tmp/result; echo $? > /tmp/exitcode; touch /tmp/done) &"
         )
-        assert "Hello" in result
+
+        ${backend}.wait_for_file("/tmp/done")
+        assert "0" == ${backend}.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
+        assert "Hello" in ${backend}.succeed("cat /tmp/result"), "no greeting in output"
       '';
     };
 
@@ -54,7 +60,13 @@ let
     testScript = ''
       machine.start()
       machine.wait_for_unit("container@test.service")
-      machine.succeed("cntr attach test sh -- -c 'ping -c5 172.16.0.1'")
+      # I haven't observed the same hanging behaviour in this version as in the OCI version which necessetates this messy invocation, but it's probably better to be safe than sorry and use it here as well
+      machine.execute(
+          "(cntr attach test sh -- -c 'ping -c5 172.16.0.1'; echo $? > /tmp/exitcode; touch /tmp/done) &"
+      )
+
+      machine.wait_for_file("/tmp/done")
+      assert "0" == machine.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
     '';
   };
 in {
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 31f8a4bcc198..10f9cb05c9cb 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -2,8 +2,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
 let
   configDir = "/var/lib/foobar";
-  mqttUsername = "homeassistant";
-  mqttPassword = "secret";
 in {
   name = "home-assistant";
   meta.maintainers = lib.teams.home-assistant.members;
@@ -11,18 +9,6 @@ in {
   nodes.hass = { pkgs, ... }: {
     environment.systemPackages = with pkgs; [ mosquitto ];
 
-    services.mosquitto = {
-      enable = true;
-      listeners = [ {
-        users = {
-          "${mqttUsername}" = {
-            acl = [ "readwrite #" ];
-            password = mqttPassword;
-          };
-        };
-      } ];
-    };
-
     services.postgresql = {
       enable = true;
       ensureDatabases = [ "hass" ];
@@ -76,23 +62,6 @@ in {
         # https://www.home-assistant.io/integrations/frontend/
         frontend = {};
 
-        # configure an mqtt broker connection
-        # https://www.home-assistant.io/integrations/mqtt
-        mqtt = {
-          broker = "127.0.0.1";
-          username = mqttUsername;
-          password = mqttPassword;
-        };
-
-        # create a mqtt sensor that syncs state with its mqtt topic
-        # https://www.home-assistant.io/integrations/sensor.mqtt/
-        binary_sensor = [ {
-          platform = "mqtt";
-          state_topic = "home-assistant/test";
-          payload_on = "let_there_be_light";
-          payload_off = "off";
-        } ];
-
         # set up a wake-on-lan switch to test capset capability required
         # for the ping suid wrapper
         # https://www.home-assistant.io/integrations/wake_on_lan/
@@ -109,11 +78,9 @@ in {
           listen_port = 80;
         };
 
-        # show mqtt interaction in the log
         # https://www.home-assistant.io/integrations/logger/
         logger = {
           default = "info";
-          logs."homeassistant.components.mqtt" = "debug";
         };
       };
 
@@ -167,12 +134,6 @@ in {
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
-    with subtest("Toggle a binary sensor using MQTT"):
-        hass.wait_for_open_port(1883)
-        hass.succeed(
-            "mosquitto_pub -V mqttv5 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light"
-        )
-
     with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
         hass.wait_for_open_port(80)
         hass.succeed("curl --fail http://localhost:80/description.xml")
@@ -188,10 +149,6 @@ in {
     with subtest("Check that no errors were logged"):
         assert "ERROR" not in output_log
 
-    # example line: 2020-06-20 10:01:32 DEBUG (MainThread) [homeassistant.components.mqtt] Received message on home-assistant/test: b'let_there_be_light'
-    with subtest("Check we received the mosquitto message"):
-        assert "let_there_be_light" in output_log
-
     with subtest("Check systemd unit hardening"):
         hass.log(hass.succeed("systemctl cat home-assistant.service"))
         hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
diff --git a/nixos/tests/input-remapper.nix b/nixos/tests/input-remapper.nix
new file mode 100644
index 000000000000..f692564caa57
--- /dev/null
+++ b/nixos/tests/input-remapper.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  {
+    name = "input-remapper";
+    meta = {
+      maintainers = with pkgs.lib.maintainers; [ LunNova ];
+    };
+
+    machine = { config, ... }:
+      let user = config.users.users.sybil; in
+      {
+        imports = [
+          ./common/user-account.nix
+          ./common/x11.nix
+        ];
+
+        services.xserver.enable = true;
+        services.input-remapper.enable = true;
+        users.users.sybil = { isNormalUser = true; group = "wheel"; };
+        test-support.displayManager.auto.user = user.name;
+        # workaround for pkexec not working in the test environment
+        # Error creating textual authentication agent:
+        #   Error opening current controlling terminal for the process (`/dev/tty'):
+        #   No such device or address
+        # passwordless pkexec with polkit module also doesn't work
+        # to allow the program to run, we replace pkexec with sudo
+        # and turn on passwordless sudo
+        # this is not correct in general but good enough for this test
+        security.sudo = { enable = true; wheelNeedsPassword = false; };
+        security.wrappers.pkexec = pkgs.lib.mkForce
+          {
+            setuid = true;
+            owner = "root";
+            group = "root";
+            source = "${pkgs.sudo}/bin/sudo";
+          };
+      };
+
+    enableOCR = true;
+
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_x()
+
+      machine.succeed("systemctl status input-remapper.service")
+      machine.execute("su - sybil -c input-remapper-gtk >&2 &")
+
+      machine.wait_for_text("Input Remapper")
+      machine.wait_for_text("Preset")
+      machine.wait_for_text("Change Key")
+    '';
+  })
diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix
index 1be3fed6acc9..6367ed808e06 100644
--- a/nixos/tests/keycloak.nix
+++ b/nixos/tests/keycloak.nix
@@ -40,7 +40,7 @@ let
 
           environment.systemPackages = with pkgs; [
             xmlstarlet
-            libtidy
+            html-tidy
             jq
           ];
         };
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index f0c72084be5c..d4410beb937e 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/nixos/tests/kubernetes/base.nix
@@ -18,7 +18,7 @@ let
         ${master.ip}  api.${domain}
         ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip}  ${machineName}.${domain}") (attrNames machines)}
       '';
-      kubectl = with pkgs; runCommand "wrap-kubectl" { buildInputs = [ makeWrapper ]; } ''
+      wrapKubectl = with pkgs; runCommand "wrap-kubectl" { buildInputs = [ makeWrapper ]; } ''
         mkdir -p $out/bin
         makeWrapper ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl --set KUBECONFIG "/etc/kubernetes/cluster-admin.kubeconfig"
       '';
@@ -48,7 +48,7 @@ let
                 };
               };
               programs.bash.enableCompletion = true;
-              environment.systemPackages = [ kubectl ];
+              environment.systemPackages = [ wrapKubectl ];
               services.flannel.iface = "eth1";
               services.kubernetes = {
                 proxy.hostname = "${masterName}.${domain}";
diff --git a/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix
index ca73562256e4..9e73fbbd32a8 100644
--- a/nixos/tests/kubernetes/rbac.nix
+++ b/nixos/tests/kubernetes/rbac.nix
@@ -76,7 +76,7 @@ let
     }];
   });
 
-  kubectl = pkgs.runCommand "copy-kubectl" { buildInputs = [ pkgs.kubernetes ]; } ''
+  copyKubectl = pkgs.runCommand "copy-kubectl" { } ''
     mkdir -p $out/bin
     cp ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl
   '';
@@ -84,7 +84,7 @@ let
   kubectlImage = pkgs.dockerTools.buildImage {
     name = "kubectl";
     tag = "latest";
-    contents = [ kubectl pkgs.busybox kubectlPod2 ];
+    contents = [ copyKubectl pkgs.busybox kubectlPod2 ];
     config.Entrypoint = ["/bin/sh"];
   };
 
diff --git a/nixos/tests/logrotate.nix b/nixos/tests/logrotate.nix
index 0f6b59f071d4..38da8d535275 100644
--- a/nixos/tests/logrotate.nix
+++ b/nixos/tests/logrotate.nix
@@ -15,19 +15,21 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
       with subtest("whether logrotate works"):
           machine.succeed(
               # we must rotate once first to create logrotate stamp
-              "systemctl start --wait logrotate.service",
+              "systemctl start logrotate.service")
+          # we need to wait for console text once here to
+          # clear console buffer up to this point for next wait
+          machine.wait_for_console_text('logrotate.service: Deactivated successfully')
 
+          machine.succeed(
               # wtmp is present in default config.
               "rm -f /var/log/wtmp*",
-              "echo test > /var/log/wtmp",
-
-              # move into the future and rotate
-              "date -s 'now + 1 month + 1 day'",
-              # systemd will run logrotate from logrotate.timer automatically
-              # on date change, but if we want to wait for it to terminate
-              # it's easier to run again...
-              "systemctl start --wait logrotate.service",
+              # we need to give it at least 1MB
+              "dd if=/dev/zero of=/var/log/wtmp bs=2M count=1",
 
+              # move into the future and check rotation.
+              "date -s 'now + 1 month + 1 day'")
+          machine.wait_for_console_text('logrotate.service: Deactivated successfully')
+          machine.succeed(
               # check rotate worked
               "[ -e /var/log/wtmp.1 ]",
           )
diff --git a/nixos/tests/matrix-appservice-irc.nix b/nixos/tests/matrix-appservice-irc.nix
index e1da410af060..d1c561f95dbf 100644
--- a/nixos/tests/matrix-appservice-irc.nix
+++ b/nixos/tests/matrix-appservice-irc.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... }:
   let
-    homeserverUrl = "http://homeserver:8448";
+    homeserverUrl = "http://homeserver:8008";
   in
   {
     name = "matrix-appservice-irc";
@@ -14,28 +14,32 @@ import ./make-test-python.nix ({ pkgs, ... }:
         specialisation.running.configuration = {
           services.matrix-synapse = {
             enable = true;
-            database_type = "sqlite3";
-            app_service_config_files = [ "/registration.yml" ];
-
-            enable_registration = true;
-
-            listeners = [
-              # The default but tls=false
-              {
-                "bind_address" = "";
-                "port" = 8448;
-                "resources" = [
-                  { "compress" = true; "names" = [ "client" ]; }
-                  { "compress" = false; "names" = [ "federation" ]; }
+            settings = {
+              database.name = "sqlite3";
+              app_service_config_files = [ "/registration.yml" ];
+
+              enable_registration = true;
+
+              listeners = [ {
+                # The default but tls=false
+                bind_addresses = [
+                  "0.0.0.0"
                 ];
-                "tls" = false;
-                "type" = "http";
-                "x_forwarded" = false;
-              }
-            ];
+                port = 8008;
+                resources = [ {
+                  "compress" = true;
+                  "names" = [ "client" ];
+                } {
+                  "compress" = false;
+                  "names" = [ "federation" ];
+                } ];
+                tls = false;
+                type = "http";
+              } ];
+            };
           };
 
-          networking.firewall.allowedTCPPorts = [ 8448 ];
+          networking.firewall.allowedTCPPorts = [ 8008 ];
         };
       };
 
@@ -209,7 +213,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
           )
 
           homeserver.wait_for_unit("matrix-synapse.service")
-          homeserver.wait_for_open_port(8448)
+          homeserver.wait_for_open_port(8008)
 
       with subtest("ensure messages can be exchanged"):
           client.succeed("do_test ${homeserverUrl} >&2")
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index 21e8c24e4713..1ff1e47b2840 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -33,6 +33,29 @@ import ./make-test-python.nix ({ pkgs, ... } : let
   testUser = "alice";
   testPassword = "alicealice";
   testEmail = "alice@example.com";
+
+  listeners = [ {
+    port = 8448;
+    bind_addresses = [
+      "127.0.0.1"
+      "::1"
+    ];
+    type = "http";
+    tls = true;
+    x_forwarded = false;
+    resources = [ {
+      names = [
+        "client"
+      ];
+      compress = true;
+    } {
+      names = [
+        "federation"
+      ];
+      compress = false;
+    } ];
+  } ];
+
 in {
 
   name = "matrix-synapse";
@@ -48,22 +71,24 @@ in {
     {
       services.matrix-synapse = {
         enable = true;
-        database_type = "psycopg2";
-        tls_certificate_path = "${cert}";
-        tls_private_key_path = "${key}";
-        database_args = {
-          password = "synapse";
+        settings = {
+          inherit listeners;
+          database = {
+            name = "psycopg2";
+            args.password = "synapse";
+          };
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+          registration_shared_secret = registrationSharedSecret;
+          public_baseurl = "https://example.com";
+          email = {
+            smtp_host = mailerDomain;
+            smtp_port = 25;
+            require_transport_security = true;
+            notif_from = "matrix <matrix@${mailerDomain}>";
+            app_name = "Matrix";
+          };
         };
-        registration_shared_secret = registrationSharedSecret;
-        public_baseurl = "https://example.com";
-        extraConfig = ''
-          email:
-            smtp_host: "${mailerDomain}"
-            smtp_port: 25
-            require_transport_security: true
-            notif_from: "matrix <matrix@${mailerDomain}>"
-            app_name: "Matrix"
-        '';
       };
       services.postgresql = {
         enable = true;
@@ -165,9 +190,12 @@ in {
     serversqlite = args: {
       services.matrix-synapse = {
         enable = true;
-        database_type = "sqlite3";
-        tls_certificate_path = "${cert}";
-        tls_private_key_path = "${key}";
+        settings = {
+          inherit listeners;
+          database.name = "sqlite3";
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+        };
       };
     };
   };
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
index bb55f6f5440b..54094ab9d611 100644
--- a/nixos/tests/matrix/mjolnir.nix
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -38,26 +38,31 @@ import ../make-test-python.nix (
       homeserver = { pkgs, ... }: {
         services.matrix-synapse = {
           enable = true;
-          database_type = "sqlite3";
-          tls_certificate_path = "${cert}";
-          tls_private_key_path = "${key}";
-          enable_registration = true;
-          registration_shared_secret = "supersecret-registration";
-
-          listeners = [
-            # The default but tls=false
-            {
-              "bind_address" = "";
-              "port" = 8448;
-              "resources" = [
-                { "compress" = true; "names" = [ "client" "webclient" ]; }
-                { "compress" = false; "names" = [ "federation" ]; }
+          settings = {
+            database.name = "sqlite3";
+            tls_certificate_path = "${cert}";
+            tls_private_key_path = "${key}";
+            enable_registration = true;
+            registration_shared_secret = "supersecret-registration";
+
+            listeners = [ {
+              # The default but tls=false
+              bind_addresses = [
+                "0.0.0.0"
               ];
-              "tls" = false;
-              "type" = "http";
-              "x_forwarded" = false;
-            }
-          ];
+              port = 8448;
+              resources = [ {
+                compress = true;
+                names = [ "client" ];
+              } {
+                compress = false;
+                names = [ "federation" ];
+              } ];
+              tls = false;
+              type = "http";
+              x_forwarded = false;
+            } ];
+          };
         };
 
         networking.firewall.allowedTCPPorts = [ 8448 ];
diff --git a/nixos/tests/matrix/pantalaimon.nix b/nixos/tests/matrix/pantalaimon.nix
index fcb9904b2138..1a9894dd2159 100644
--- a/nixos/tests/matrix/pantalaimon.nix
+++ b/nixos/tests/matrix/pantalaimon.nix
@@ -47,9 +47,32 @@ import ../make-test-python.nix (
 
       services.matrix-synapse = {
         enable = true;
-        database_type = "sqlite3";
-        tls_certificate_path = "${cert}";
-        tls_private_key_path = "${key}";
+        settings = {
+          listeners = [ {
+            port = 8448;
+            bind_addresses = [
+              "127.0.0.1"
+              "::1"
+            ];
+            type = "http";
+            tls = true;
+            x_forwarded = false;
+            resources = [ {
+              names = [
+                "client"
+              ];
+              compress = true;
+            } {
+              names = [
+                "federation"
+              ];
+              compress = false;
+            } ];
+          } ];
+          database.name = "sqlite3";
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+        };
       };
     };
 
diff --git a/nixos/tests/nano.nix b/nixos/tests/nano.nix
deleted file mode 100644
index 6585a6842e85..000000000000
--- a/nixos/tests/nano.nix
+++ /dev/null
@@ -1,44 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "nano";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { lib, ... }: {
-    environment.systemPackages = [ pkgs.nano ];
-  };
-
-  testScript = { ... }: ''
-    start_all()
-
-    with subtest("Create user and log in"):
-        machine.wait_for_unit("multi-user.target")
-        machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
-        machine.succeed("useradd -m alice")
-        machine.succeed("(echo foobar; echo foobar) | passwd alice")
-        machine.wait_until_tty_matches(1, "login: ")
-        machine.send_chars("alice\n")
-        machine.wait_until_tty_matches(1, "login: alice")
-        machine.wait_until_succeeds("pgrep login")
-        machine.wait_until_tty_matches(1, "Password: ")
-        machine.send_chars("foobar\n")
-        machine.wait_until_succeeds("pgrep -u alice bash")
-        machine.screenshot("prompt")
-
-    with subtest("Use nano"):
-        machine.send_chars("nano /tmp/foo")
-        machine.send_key("ret")
-        machine.sleep(2)
-        machine.send_chars("42")
-        machine.sleep(1)
-        machine.send_key("ctrl-x")
-        machine.sleep(1)
-        machine.send_key("y")
-        machine.sleep(1)
-        machine.screenshot("nano")
-        machine.sleep(1)
-        machine.send_key("ret")
-        machine.wait_for_file("/tmp/foo")
-        assert "42" in machine.succeed("cat /tmp/foo")
-  '';
-})
diff --git a/nixos/tests/nats.nix b/nixos/tests/nats.nix
index bee36f262f4c..c650904e53bf 100644
--- a/nixos/tests/nats.nix
+++ b/nixos/tests/nats.nix
@@ -45,21 +45,19 @@ in import ./make-test-python.nix ({ pkgs, lib, ... }: {
             "{}"
         ).format(" ".join(args))
 
+    def parallel(*fns):
+        from threading import Thread
+        threads = [ Thread(target=fn) for fn in fns ]
+        for t in threads: t.start()
+        for t in threads: t.join()
+
     start_all()
     server.wait_for_unit("nats.service")
 
-    client1.fail("test -f ${file}")
-
-    # Subscribe on topic on client1 and echo messages to file.
-    client1.execute("({} | tee ${file} &)".format(nats_cmd("sub", "--raw", "${topic}")))
-
-    # Give client1 some time to subscribe.
-    client1.execute("sleep 2")
-
-    # Publish message on client2.
-    client2.execute(nats_cmd("pub", "${topic}", "hello"))
-
-    # Check if message has been received.
-    client1.succeed("grep -q hello ${file}")
+    with subtest("pub sub"):
+        parallel(
+            lambda: client1.succeed(nats_cmd("sub", "--count", "1", "${topic}")),
+            lambda: client2.succeed("sleep 2 && {}".format(nats_cmd("pub", "${topic}", "hello"))),
+        )
   '';
 })
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 2d68bc859df5..8c9df19f2d58 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -868,7 +868,7 @@ let
         print(client.succeed("ip l add name foo type dummy"))
         print(client.succeed("stat /etc/systemd/network/50-foo.link"))
         client.succeed("udevadm settle")
-        assert "mtu 1442" in client.succeed("ip l show dummy0")
+        assert "mtu 1442" in client.succeed("ip l show dev foo")
       '';
     };
     wlanInterface = let
diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
index b25fc95f4b30..f0834c51f0b4 100644
--- a/nixos/tests/nixops/default.nix
+++ b/nixos/tests/nixops/default.nix
@@ -9,7 +9,7 @@ let
     #  - Alternatively, blocked on a NixOps 2 release
     #    https://github.com/NixOS/nixops/issues/1242
     # stable = testsLegacyNetwork { nixopsPkg = pkgs.nixops; };
-    unstable = testsForPackage { nixopsPkg = pkgs.nixopsUnstable; };
+    unstable = testsForPackage { nixopsPkg = pkgs.nixops_unstable; };
 
     # inherit testsForPackage;
   };
diff --git a/nixos/tests/pgadmin4-standalone.nix b/nixos/tests/pgadmin4-standalone.nix
new file mode 100644
index 000000000000..442570c5306b
--- /dev/null
+++ b/nixos/tests/pgadmin4-standalone.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  # This is seperate from pgadmin4 since we don't want both running at once
+
+  {
+    name = "pgadmin4-standalone";
+    meta.maintainers = with lib.maintainers; [ mkg20001 ];
+
+    nodes.machine = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [
+        curl
+      ];
+
+      services.postgresql = {
+        enable = true;
+
+        authentication = ''
+          host    all             all             localhost               trust
+        '';
+
+        ensureUsers = [
+          {
+            name = "postgres";
+            ensurePermissions = {
+              "DATABASE \"postgres\"" = "ALL PRIVILEGES";
+            };
+          }
+        ];
+      };
+
+      services.pgadmin = {
+        enable = true;
+        initialEmail = "bruh@localhost.de";
+        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("postgresql")
+      machine.wait_for_unit("pgadmin")
+
+      machine.wait_until_succeeds("curl -s localhost:5050")
+    '';
+  })
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
new file mode 100644
index 000000000000..658315d3ac0c
--- /dev/null
+++ b/nixos/tests/pgadmin4.nix
@@ -0,0 +1,142 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+  let
+    pgadmin4SrcDir = "/pgadmin";
+    pgadmin4Dir = "/var/lib/pgadmin";
+    pgadmin4LogDir = "/var/log/pgadmin";
+
+    python-with-needed-packages = pkgs.python3.withPackages (ps: with ps; [
+      selenium
+      testtools
+      testscenarios
+      flask
+      flask-babelex
+      flask-babel
+      flask-gravatar
+      flask_login
+      flask_mail
+      flask_migrate
+      flask_sqlalchemy
+      flask_wtf
+      flask-compress
+      passlib
+      pytz
+      simplejson
+      six
+      sqlparse
+      wtforms
+      flask-paranoid
+      psutil
+      psycopg2
+      python-dateutil
+      sqlalchemy
+      itsdangerous
+      flask-security-too
+      bcrypt
+      cryptography
+      sshtunnel
+      ldap3
+      gssapi
+      flask-socketio
+      eventlet
+      httpagentparser
+      user-agents
+      wheel
+      authlib
+      qrcode
+      pillow
+      pyotp
+    ]);
+  in
+  {
+    name = "pgadmin4";
+    meta.maintainers = with lib.maintainers; [ gador ];
+
+    nodes.machine = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ];
+      environment.systemPackages = with pkgs; [
+        pgadmin4
+        postgresql
+        python-with-needed-packages
+        chromedriver
+        chromium
+      ];
+      services.postgresql = {
+        enable = true;
+        authentication = ''
+          host    all             all             localhost               trust
+        '';
+        ensureUsers = [
+          {
+            name = "postgres";
+            ensurePermissions = {
+              "DATABASE \"postgres\"" = "ALL PRIVILEGES";
+            };
+          }
+        ];
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("postgresql")
+
+      # pgadmin4 needs its data and log directories
+      machine.succeed(
+          "mkdir -p ${pgadmin4Dir} \
+          && mkdir -p ${pgadmin4LogDir} \
+          && mkdir -p ${pgadmin4SrcDir}"
+      )
+
+      machine.succeed(
+           "tar xvzf ${pkgs.pgadmin4.src} -C ${pgadmin4SrcDir}"
+      )
+
+      machine.wait_for_file("${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/README.md")
+
+      # set paths and config for tests
+      machine.succeed(
+           "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \
+           && cp -v web/regression/test_config.json.in web/regression/test_config.json \
+           && sed -i 's|PostgreSQL 9.4|PostgreSQL|' web/regression/test_config.json \
+           && sed -i 's|/opt/PostgreSQL/9.4/bin/|${pkgs.postgresql}/bin|' web/regression/test_config.json \
+           && sed -i 's|\"headless_chrome\": false|\"headless_chrome\": true|' web/regression/test_config.json"
+      )
+
+      # adapt chrome config to run within a sandbox without GUI
+      # see https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t#50642913
+      # add chrome binary path. use spaces to satisfy python indention (tabs throw an error)
+      # this works for selenium 3 (currently used), but will need to be updated
+      # to work with "from selenium.webdriver.chrome.service import Service" in selenium 4
+      machine.succeed(
+           "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \
+           && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.binary_location = \"${pkgs.chromium}/bin/chromium\"' web/regression/runtests.py \
+           && sed -i '\|options.add_argument(\"--no-sandbox\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--headless\")' web/regression/runtests.py \
+           && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--disable-dev-shm-usage\")' web/regression/runtests.py \
+           && sed -i 's|(chrome_options=options)|(executable_path=\"${pkgs.chromedriver}/bin/chromedriver\", chrome_options=options)|' web/regression/runtests.py \
+           && sed -i 's|driver_local.maximize_window()||' web/regression/runtests.py"
+      )
+
+      # don't bother to test LDAP authentification
+      with subtest("run browser test"):
+          machine.succeed(
+               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
+               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg browser --exclude \
+               browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login'
+          )
+
+      # fontconfig is necessary for chromium to run
+      # https://github.com/NixOS/nixpkgs/issues/136207
+      with subtest("run feature test"):
+          machine.succeed(
+              'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
+               && export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [];}} \
+               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg feature_tests'
+          )
+
+      with subtest("run resql test"):
+          machine.succeed(
+               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
+               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg resql'
+          )
+    '';
+  })
diff --git a/nixos/tests/podman/default.nix b/nixos/tests/podman/default.nix
index b52a7f060ad6..67c7823c5a31 100644
--- a/nixos/tests/podman/default.nix
+++ b/nixos/tests/podman/default.nix
@@ -126,7 +126,7 @@ import ../make-test-python.nix (
           podman.succeed("docker network create default")
           podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
-            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10"
           )
           podman.succeed("docker ps | grep sleeping")
           podman.succeed("podman ps | grep sleeping")
diff --git a/nixos/tests/podman/tls-ghostunnel.nix b/nixos/tests/podman/tls-ghostunnel.nix
index c0bc47cc40b1..268a55701ccf 100644
--- a/nixos/tests/podman/tls-ghostunnel.nix
+++ b/nixos/tests/podman/tls-ghostunnel.nix
@@ -129,7 +129,7 @@ import ../make-test-python.nix (
           podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
 
           client.succeed(
-            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10"
           )
           client.succeed("docker ps | grep sleeping")
           podman.succeed("docker ps | grep sleeping")
diff --git a/nixos/tests/shiori.nix b/nixos/tests/shiori.nix
index 418bee43c939..6c59c394009e 100644
--- a/nixos/tests/shiori.nix
+++ b/nixos/tests/shiori.nix
@@ -12,7 +12,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
     authJSON = pkgs.writeText "auth.json" (builtins.toJSON {
       username = "shiori";
       password = "gopher";
-      remember = 1; # hour
       owner = true;
     });
 
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 4f297b6521d1..78eb71f0a28e 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -1,6 +1,46 @@
 # Test configuration switching.
 
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : let
+
+  # Simple service that can either be socket-activated or that will
+  # listen on port 1234 if not socket-activated.
+  # A connection to the socket causes 'hello' to be written to the client.
+  socketTest = pkgs.writeScript "socket-test.py" /* python */ ''
+    #!${pkgs.python3}/bin/python3
+
+    from socketserver import TCPServer, StreamRequestHandler
+    import socket
+    import os
+
+
+    class Handler(StreamRequestHandler):
+        def handle(self):
+            self.wfile.write("hello".encode("utf-8"))
+
+
+    class Server(TCPServer):
+        def __init__(self, server_address, handler_cls):
+            listenFds = os.getenv('LISTEN_FDS')
+            if listenFds is None or int(listenFds) < 1:
+                print(f'Binding to {server_address}')
+                TCPServer.__init__(
+                        self, server_address, handler_cls, bind_and_activate=True)
+            else:
+                TCPServer.__init__(
+                        self, server_address, handler_cls, bind_and_activate=False)
+                # Override socket
+                print(f'Got activated by {os.getenv("LISTEN_FDNAMES")} '
+                      f'with {listenFds} FDs')
+                self.socket = socket.fromfd(3, self.address_family,
+                                            self.socket_type)
+
+
+    if __name__ == "__main__":
+        server = Server(("localhost", 1234), Handler)
+        server.serve_forever()
+  '';
+
+in {
   name = "switch-test";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ gleber das_j ];
@@ -8,6 +48,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
   nodes = {
     machine = { pkgs, lib, ... }: {
+      environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff
       users.mutableUsers = false;
 
       specialisation = rec {
@@ -231,6 +272,40 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test";
         };
 
+        simple-socket.configuration = {
+          systemd.services.socket-activated = {
+            description = "A socket-activated service";
+            stopIfChanged = lib.mkDefault false;
+            serviceConfig = {
+              ExecStart = socketTest;
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+          systemd.sockets.socket-activated = {
+            wantedBy = [ "sockets.target" ];
+            listenStreams = [ "/run/test.sock" ];
+            socketConfig.SocketMode = lib.mkDefault "0777";
+          };
+        };
+
+        simple-socket-service-modified.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated.serviceConfig.X-Test = "test";
+        };
+
+        simple-socket-stop-if-changed.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated.stopIfChanged = true;
+        };
+
+        simple-socket-stop-if-changed-and-reloadtrigger.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated = {
+            stopIfChanged = true;
+            reloadTriggers = [ "test" ];
+          };
+        };
+
         mount.configuration = {
           systemd.mounts = [
             {
@@ -283,6 +358,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           systemd.services.test-watch = {
             serviceConfig = {
               Type = "oneshot";
+              RemainAfterExit = true;
               ExecStart = "${pkgs.coreutils}/bin/touch /testpath-modified";
             };
           };
@@ -377,7 +453,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Start a simple service
         out = switch_to_specialisation("${machine}", "simpleService")
@@ -387,7 +462,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: test.service\n")
-        assert_lacks(out, "as well:")
 
         # Not changing anything doesn't do anything
         out = switch_to_specialisation("${machine}", "simpleService")
@@ -397,7 +471,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Restart the simple service
         out = switch_to_specialisation("${machine}", "simpleServiceModified")
@@ -407,7 +480,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_contains(out, "\nstarting the following units: test.service\n")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Restart the service with stopIfChanged=false
         out = switch_to_specialisation("${machine}", "simpleServiceNostop")
@@ -417,7 +489,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Reload the service with reloadIfChanged=true
         out = switch_to_specialisation("${machine}", "simpleServiceReload")
@@ -427,7 +498,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Nothing happens when restartIfChanged=false
         out = switch_to_specialisation("${machine}", "simpleServiceNorestart")
@@ -437,7 +507,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Dry mode shows different messages
         out = switch_to_specialisation("${machine}", "simpleService", action="dry-activate")
@@ -447,7 +516,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
         assert_contains(out, "would start the following units: test.service\n")
 
         # Ensure \ works in unit names
@@ -458,7 +526,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: escaped\\x2ddash.service\n")
-        assert_lacks(out, "as well:")
 
         out = switch_to_specialisation("${machine}", "unitWithBackslashModified")
         assert_contains(out, "stopping the following units: escaped\\x2ddash.service\n")
@@ -467,7 +534,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_contains(out, "\nstarting the following units: escaped\\x2ddash.service\n")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
     with subtest("failing units"):
         # Let the simple service fail
@@ -481,7 +547,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "the following new units were started:")
         assert_contains(out, "warning: the following units failed: test.service\n")
         assert_contains(out, "Main PID:")  # output of systemctl
-        assert_lacks(out, "as well:")
 
         # A unit that gets into autorestart without failing is not treated as failed
         out = switch_to_specialisation("${machine}", "autorestartService")
@@ -491,7 +556,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: autorestart.service\n")
-        assert_lacks(out, "as well:")
         machine.systemctl('stop autorestart.service')  # cancel the 20y timer
 
         # Switching to the same system should do nothing (especially not treat the unit as failed)
@@ -502,7 +566,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: autorestart.service\n")
-        assert_lacks(out, "as well:")
         machine.systemctl('stop autorestart.service')  # cancel the 20y timer
 
         # If systemd thinks the unit has failed and is in autorestart, we should show it as failed
@@ -515,7 +578,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "the following new units were started:")
         assert_contains(out, "warning: the following units failed: autorestart.service\n")
         assert_contains(out, "Main PID:")  # output of systemctl
-        assert_lacks(out, "as well:")
 
     with subtest("unit file parser"):
         # Switch to a well-known state
@@ -529,7 +591,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Rename it
         out = switch_to_specialisation("${machine}", "simpleServiceWithExtraSectionOtherName")
@@ -539,7 +600,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Remove it
         out = switch_to_specialisation("${machine}", "simpleServiceNostop")
@@ -549,7 +609,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # [Install] section is ignored
         out = switch_to_specialisation("${machine}", "simpleServiceWithInstallSection")
@@ -559,7 +618,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Add a key
         out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKey")
@@ -569,7 +627,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Change its value
         out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherValue")
@@ -579,7 +636,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Rename it
         out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherName")
@@ -589,7 +645,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Remove it
         out = switch_to_specialisation("${machine}", "simpleServiceNostop")
@@ -599,7 +654,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Add a reload trigger
         out = switch_to_specialisation("${machine}", "simpleServiceReloadTrigger")
@@ -609,7 +663,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Modify the reload trigger
         out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModified")
@@ -619,7 +672,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Modify the reload trigger and something else
         out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedAndSomethingElse")
@@ -629,7 +681,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "\nrestarting the following units: test.service\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
         # Remove the reload trigger
         out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedSomethingElse")
@@ -639,7 +690,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
 
     with subtest("restart and reload by activation script"):
         switch_to_specialisation("${machine}", "simpleServiceNorestart")
@@ -649,7 +699,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "reloading the following units:")
         assert_lacks(out, "restarting the following units:")
         assert_contains(out, "\nstarting the following units: no-restart-service.service, reload-triggers-and-restart-by-as.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
-        assert_lacks(out, "as well:")
+        assert_contains(out, "the following new units were started: no-restart-service.service, reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n")
         # Switch to the same system where the example services get restarted
         # and reloaded by the activation script
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
@@ -658,7 +708,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "reloading the following units: reload-triggers-and-restart.service, reload-triggers.service, simple-reload-service.service\n")
         assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, simple-restart-service.service, simple-service.service\n")
         assert_lacks(out, "\nstarting the following units:")
-        assert_lacks(out, "as well:")
+        assert_lacks(out, "the following new units were started:")
         # Switch to the same system and see if the service gets restarted when it's modified
         # while the fact that it's supposed to be reloaded by the activation script is ignored.
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified")
@@ -667,7 +717,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "reloading the following units: reload-triggers.service, simple-reload-service.service\n")
         assert_contains(out, "restarting the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
         assert_lacks(out, "\nstarting the following units:")
-        assert_lacks(out, "as well:")
+        assert_lacks(out, "the following new units were started:")
         # The same, but in dry mode
         out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate")
         assert_lacks(out, "would stop the following units:")
@@ -675,7 +725,71 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "would reload the following units: reload-triggers.service, simple-reload-service.service\n")
         assert_contains(out, "would restart the following units: reload-triggers-and-restart-by-as.service, reload-triggers-and-restart.service, simple-restart-service.service, simple-service.service\n")
         assert_lacks(out, "\nwould start the following units:")
-        assert_lacks(out, "as well:")
+
+    with subtest("socket-activated services"):
+        # Socket-activated services don't get started, just the socket
+        machine.fail("[ -S /run/test.sock ]")
+        out = switch_to_specialisation("${machine}", "simple-socket")
+        # assert_lacks(out, "stopping the following units:") not relevant
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: socket-activated.socket\n")
+        machine.succeed("[ -S /run/test.sock ]")
+
+        # Changing a non-activated service does nothing
+        out = switch_to_specialisation("${machine}", "simple-socket-service-modified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # The unit is properly activated when the socket is accessed
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated")  # idk how that would happen tbh
+
+        # Changing an activated service with stopIfChanged=false restarts the service
+        out = switch_to_specialisation("${machine}", "simple-socket")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: socket-activated.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
+
+        # Changing an activated service with stopIfChanged=true stops the service and
+        # socket and starts the socket
+        out = switch_to_specialisation("${machine}", "simple-socket-stop-if-changed")
+        assert_contains(out, "stopping the following units: socket-activated.service, socket-activated.socket\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
+
+        # Changing a reload trigger of a socket-activated unit only reloads it
+        out = switch_to_specialisation("${machine}", "simple-socket-stop-if-changed-and-reloadtrigger")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: socket-activated.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units: socket-activated.socket")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
 
     with subtest("mounts"):
         switch_to_specialisation("${machine}", "mount")
@@ -688,7 +802,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
         # It changed
         out = machine.succeed("mount | grep 'on /testmount'")
         assert_contains(out, "size=10240k")
@@ -699,11 +812,11 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "OnCalendar=2014-03-25 02:59:56 UTC")
         out = switch_to_specialisation("${machine}", "timerModified")
         assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following units:")
         assert_lacks(out, "reloading the following units:")
-        assert_contains(out, "restarting the following units: test-timer.timer\n")
+        assert_contains(out, "\nrestarting the following units: test-timer.timer\n")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
-        assert_lacks(out, "as well:")
         # It changed
         out = machine.succeed("systemctl show test-timer.timer")
         assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00")
@@ -715,14 +828,14 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_lacks(out, "reloading the following units:")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
-        assert_contains(out, "the following new units were started: test-watch.path")
-        assert_lacks(out, "as well:")
+        assert_contains(out, "the following new units were started: test-watch.path\n")
         machine.fail("test -f /testpath-modified")
 
         # touch the file, unit should be triggered
         machine.succeed("touch /testpath")
         machine.wait_until_succeeds("test -f /testpath-modified")
         machine.succeed("rm /testpath /testpath-modified")
+        machine.systemctl("stop test-watch.service")
         switch_to_specialisation("${machine}", "pathModified")
         machine.succeed("touch /testpath")
         machine.fail("test -f /testpath-modified")
@@ -737,8 +850,21 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     with subtest("slices"):
         machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom")  # allow OOMing
         out = switch_to_specialisation("${machine}", "slice")
+        # assert_lacks(out, "stopping the following units:") not relevant
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
         machine.fail("systemctl start testservice.service")
+
         out = switch_to_specialisation("${machine}", "sliceModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
         machine.succeed("systemctl start testservice.service")
         machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom")  # disallow OOMing
   '';
diff --git a/nixos/tests/systemd-confinement.nix b/nixos/tests/systemd-confinement.nix
index 8fafb11e1e8c..3181af309a6e 100644
--- a/nixos/tests/systemd-confinement.nix
+++ b/nixos/tests/systemd-confinement.nix
@@ -17,15 +17,19 @@ import ./make-test-python.nix {
       exit "''${ret:-1}"
     '';
 
-    mkTestStep = num: { config ? {}, testScript }: {
-      systemd.sockets."test${toString num}" = {
+    mkTestStep = num: {
+      testScript,
+      config ? {},
+      serviceName ? "test${toString num}",
+    }: {
+      systemd.sockets.${serviceName} = {
         description = "Socket for Test Service ${toString num}";
         wantedBy = [ "sockets.target" ];
         socketConfig.ListenStream = "/run/test${toString num}.sock";
         socketConfig.Accept = true;
       };
 
-      systemd.services."test${toString num}@" = {
+      systemd.services."${serviceName}@" = {
         description = "Confined Test Service ${toString num}";
         confinement = (config.confinement or {}) // { enable = true; };
         serviceConfig = (config.serviceConfig or {}) // {
@@ -135,6 +139,16 @@ import ./make-test-python.nix {
               machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek')
         '';
       }
+      { serviceName = "shipped-unitfile";
+        config.confinement.mode = "chroot-only";
+        testScript = ''
+          with subtest("check if shipped unit file still works"):
+              machine.succeed(
+                  'chroot-exec \'kill -9 $$ 2>&1 || :\' | '
+                  'grep -q "Too many levels of symbolic links"'
+              )
+        '';
+      }
     ];
 
     options.__testSteps = lib.mkOption {
@@ -143,6 +157,15 @@ import ./make-test-python.nix {
     };
 
     config.environment.systemPackages = lib.singleton testClient;
+    config.systemd.packages = lib.singleton (pkgs.writeTextFile {
+      name = "shipped-unitfile";
+      destination = "/etc/systemd/system/shipped-unitfile@.service";
+      text = ''
+        [Service]
+        SystemCallFilter=~kill
+        SystemCallErrorNumber=ELOOP
+      '';
+    });
 
     config.users.groups.chroot-testgroup = {};
     config.users.users.chroot-testuser = {
diff --git a/nixos/tests/tinywl.nix b/nixos/tests/tinywl.nix
index b286cab77945..8fb87b533306 100644
--- a/nixos/tests/tinywl.nix
+++ b/nixos/tests/tinywl.nix
@@ -10,6 +10,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       # Automatically login on tty1 as a normal user:
       imports = [ ./common/user-account.nix ];
       services.getty.autologinUser = "alice";
+      security.polkit.enable = true;
 
       environment = {
         systemPackages = with pkgs; [ tinywl foot wayland-utils ];
diff --git a/nixos/tests/vsftpd.nix b/nixos/tests/vsftpd.nix
new file mode 100644
index 000000000000..4bea27f0eb10
--- /dev/null
+++ b/nixos/tests/vsftpd.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vsftpd";
+
+  nodes = {
+    server = {
+      services.vsftpd = {
+        enable = true;
+        userlistDeny = false;
+        localUsers = true;
+        userlist = [ "ftp-test-user" ];
+        writeEnable = true;
+        localRoot = "/tmp";
+      };
+      networking.firewall.enable = false;
+
+      users = {
+        users.ftp-test-user = {
+          isSystemUser = true;
+          password = "ftp-test-password";
+          group = "ftp-test-group";
+        };
+        groups.ftp-test-group = {};
+      };
+    };
+
+    client = {};
+  };
+
+  testScript = ''
+    client.start()
+    server.wait_for_unit("vsftpd")
+    server.wait_for_open_port("21")
+
+    client.succeed("curl -u ftp-test-user:ftp-test-password ftp://server")
+    client.succeed('echo "this is a test" > /tmp/test.file.up')
+    client.succeed("curl -v -T /tmp/test.file.up -u ftp-test-user:ftp-test-password ftp://server")
+    client.succeed("curl -u ftp-test-user:ftp-test-password ftp://server/test.file.up > /tmp/test.file.down")
+    client.succeed("diff /tmp/test.file.up /tmp/test.file.down")
+    assert client.succeed("cat /tmp/test.file.up") == server.succeed("cat /tmp/test.file.up")
+    assert client.succeed("cat /tmp/test.file.down") == server.succeed("cat /tmp/test.file.up")
+  '';
+})
diff --git a/nixos/tests/zammad.nix b/nixos/tests/zammad.nix
new file mode 100644
index 000000000000..4e466f6e3b9b
--- /dev/null
+++ b/nixos/tests/zammad.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix (
+  { lib, pkgs, ... }:
+
+  {
+    name = "zammad";
+
+    meta.maintainers = with lib.maintainers; [ garbas taeer ];
+
+    nodes.machine = { config, ... }: {
+      services.zammad.enable = true;
+      services.zammad.secretKeyBaseFile = pkgs.writeText "secret" ''
+        52882ef142066e09ab99ce816ba72522e789505caba224a52d750ec7dc872c2c371b2fd19f16b25dfbdd435a4dd46cb3df9f82eb63fafad715056bdfe25740d6
+      '';
+
+      systemd.services.zammad-locale-cheat =
+        let cfg = config.services.zammad; in
+        {
+          serviceConfig = {
+            Type = "simple";
+            Restart = "always";
+
+            User = "zammad";
+            Group = "zammad";
+            PrivateTmp = true;
+            StateDirectory = "zammad";
+            WorkingDirectory = cfg.dataDir;
+          };
+          wantedBy = [ "zammad-web.service" ];
+          description = "Hack in the locale files so zammad doesn't try to access the internet";
+          script = ''
+            mkdir -p ./config/translations
+            VERSION=$(cat ${cfg.package}/VERSION)
+
+            # If these files are not in place, zammad will try to access the internet.
+            # For the test, we only need to supply en-us.
+            echo '[{"locale":"en-us","alias":"en","name":"English (United States)","active":true,"dir":"ltr"}]' \
+              > ./config/locales-$VERSION.yml
+            echo '[{"locale":"en-us","format":"time","source":"date","target":"mm/dd/yyyy","target_initial":"mm/dd/yyyy"},{"locale":"en-us","format":"time","source":"timestamp","target":"mm/dd/yyyy HH:MM","target_initial":"mm/dd/yyyy HH:MM"}]' \
+              > ./config/translations/en-us-$VERSION.yml
+          '';
+        };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("postgresql.service")
+      machine.wait_for_unit("zammad-web.service")
+      machine.wait_for_unit("zammad-websocket.service")
+      machine.wait_for_unit("zammad-scheduler.service")
+      # wait for zammad to fully come up
+      machine.sleep(120)
+
+      # without the grep the command does not produce valid utf-8 for some reason
+      with subtest("welcome screen loads"):
+          machine.succeed(
+              "curl -sSfL http://localhost:3000/ | grep '<title>Zammad Helpdesk</title>'"
+          )
+    '';
+  }
+)