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/from_md/installation/building-nixos.chapter.xml5
-rw-r--r--nixos/doc/manual/from_md/installation/installing.chapter.xml21
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2211.section.xml15
-rw-r--r--nixos/doc/manual/installation/building-nixos.chapter.md3
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md12
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md6
-rw-r--r--nixos/lib/qemu-common.nix2
-rw-r--r--nixos/modules/config/appstream.nix4
-rw-r--r--nixos/modules/config/console.nix6
-rw-r--r--nixos/modules/config/fonts/fontdir.nix8
-rw-r--r--nixos/modules/config/i18n.nix1
-rw-r--r--nixos/modules/config/locale.nix19
-rw-r--r--nixos/modules/config/nsswitch.nix24
-rw-r--r--nixos/modules/config/pulseaudio.nix26
-rw-r--r--nixos/modules/config/qt5.nix75
-rw-r--r--nixos/modules/config/unix-odbc-drivers.nix6
-rw-r--r--nixos/modules/config/xdg/autostart.nix4
-rw-r--r--nixos/modules/config/xdg/icons.nix4
-rw-r--r--nixos/modules/config/xdg/menus.nix4
-rw-r--r--nixos/modules/config/xdg/sounds.nix4
-rw-r--r--nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixos/modules/hardware/cpu/intel-sgx.nix14
-rw-r--r--nixos/modules/hardware/ksm.nix4
-rw-r--r--nixos/modules/i18n/input-method/fcitx.nix4
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix6
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix4
-rw-r--r--nixos/modules/installer/tools/tools.nix18
-rw-r--r--nixos/modules/misc/man-db.nix4
-rw-r--r--nixos/modules/misc/mandoc.nix8
-rw-r--r--nixos/modules/misc/version.nix16
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/profiles/all-hardware.nix2
-rw-r--r--nixos/modules/programs/atop.nix20
-rw-r--r--nixos/modules/programs/bash/bash.nix20
-rw-r--r--nixos/modules/programs/dconf.nix4
-rw-r--r--nixos/modules/programs/fish.nix30
-rw-r--r--nixos/modules/programs/less.nix20
-rw-r--r--nixos/modules/programs/nano.nix6
-rw-r--r--nixos/modules/programs/proxychains.nix34
-rw-r--r--nixos/modules/programs/spacefm.nix10
-rw-r--r--nixos/modules/programs/starship.nix4
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/pam.nix4
-rw-r--r--nixos/modules/security/polkit.nix8
-rw-r--r--nixos/modules/services/audio/hqplayerd.nix2
-rw-r--r--nixos/modules/services/audio/roon-server.nix4
-rw-r--r--nixos/modules/services/backup/restic.nix11
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix117
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/job-builder.nix1
-rw-r--r--nixos/modules/services/hardware/kanata.nix156
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix2
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix1
-rw-r--r--nixos/modules/services/misc/gitea.nix10
-rw-r--r--nixos/modules/services/monitoring/grafana-agent.nix22
-rw-r--r--nixos/modules/services/monitoring/netdata.nix2
-rw-r--r--nixos/modules/services/networking/headscale.nix6
-rw-r--r--nixos/modules/services/networking/murmur.nix13
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix4
-rw-r--r--nixos/modules/services/networking/stunnel.nix158
-rw-r--r--nixos/modules/services/networking/syncthing.nix14
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix31
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix62
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/default.nix3
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm.nix41
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm2.nix47
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm3.nix35
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix10
-rw-r--r--nixos/modules/system/boot/networkd.nix7
-rw-r--r--nixos/modules/system/boot/resolved.nix59
-rw-r--r--nixos/modules/system/boot/systemd.nix70
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix40
-rw-r--r--nixos/modules/system/boot/tmp.nix10
-rw-r--r--nixos/modules/system/etc/etc.nix30
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix14
-rw-r--r--nixos/modules/tasks/filesystems.nix3
-rw-r--r--nixos/modules/tasks/snapraid.nix24
-rw-r--r--nixos/modules/tasks/swraid.nix2
-rw-r--r--nixos/modules/virtualisation/anbox.nix12
-rw-r--r--nixos/modules/virtualisation/build-vm.nix8
-rw-r--r--nixos/modules/virtualisation/containers.nix20
-rw-r--r--nixos/modules/virtualisation/digital-ocean-image.nix12
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix8
-rw-r--r--nixos/modules/virtualisation/docker-rootless.nix12
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix103
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix8
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix28
-rw-r--r--nixos/modules/virtualisation/waydroid.nix2
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix24
-rw-r--r--nixos/tests/all-tests.nix4
-rw-r--r--nixos/tests/jenkins.nix6
-rw-r--r--nixos/tests/k3s/default.nix9
-rw-r--r--nixos/tests/k3s/multi-node.nix137
-rw-r--r--nixos/tests/k3s/single-node.nix (renamed from nixos/tests/k3s-single-node.nix)16
-rw-r--r--nixos/tests/kea.nix2
-rw-r--r--nixos/tests/lighttpd.nix21
-rw-r--r--nixos/tests/nextcloud/default.nix4
-rw-r--r--nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix118
-rw-r--r--nixos/tests/nginx-etag.nix4
-rw-r--r--nixos/tests/podgrab.nix4
-rw-r--r--nixos/tests/restic.nix9
-rw-r--r--nixos/tests/signal-desktop.nix2
-rw-r--r--nixos/tests/stunnel.nix174
-rw-r--r--nixos/tests/vaultwarden.nix31
106 files changed, 1484 insertions, 809 deletions
diff --git a/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml b/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
index e7a76a6d715d8..ea2d01bebcc2f 100644
--- a/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
@@ -33,9 +33,14 @@
   </para>
   <section xml:id="sec-building-image-instructions">
     <title>Practical Instructions</title>
+    <para>
+      To build an ISO image for the channel
+      <literal>nixos-unstable</literal>:
+    </para>
     <programlisting>
 $ git clone https://github.com/NixOS/nixpkgs.git
 $ cd nixpkgs/nixos
+$ git switch nixos-unstable
 $ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix
 </programlisting>
     <para>
diff --git a/nixos/doc/manual/from_md/installation/installing.chapter.xml b/nixos/doc/manual/from_md/installation/installing.chapter.xml
index 19ff841f5a679..0fcbcf2e66c27 100644
--- a/nixos/doc/manual/from_md/installation/installing.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/installing.chapter.xml
@@ -426,7 +426,9 @@ OK
             </term>
             <listitem>
               <para>
-                You <emphasis>must</emphasis> set the option
+                You must select a boot-loader, either system-boot or
+                GRUB. The recommended option is systemd-boot: set the
+                option
                 <xref linkend="opt-boot.loader.systemd-boot.enable" />
                 to <literal>true</literal>.
                 <literal>nixos-generate-config</literal> should do this
@@ -440,6 +442,23 @@ OK
                 <link linkend="opt-boot.loader.systemd-boot.enable"><literal>boot.loader.systemd-boot</literal></link>
                 as well.
               </para>
+              <para>
+                If you want to use GRUB, set
+                <xref linkend="opt-boot.loader.grub.device" /> to
+                <literal>nodev</literal> and
+                <xref linkend="opt-boot.loader.grub.efiSupport" /> to
+                <literal>true</literal>.
+              </para>
+              <para>
+                With system-boot, you should not need any special
+                configuration to detect other installed systems. With
+                GRUB, set
+                <xref linkend="opt-boot.loader.grub.useOSProber" /> to
+                <literal>true</literal>, but this will only detect
+                windows partitions, not other linux distributions. If
+                you dual boot another linux distribution, use
+                system-boot instead.
+              </para>
             </listitem>
           </varlistentry>
         </variablelist>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 864b6e47db264..79f856a4093a0 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -135,6 +135,14 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/jtroo/kanata">kanata</link>,
+          a tool to improve keyboard comfort and usability with advanced
+          customization. Available as
+          <link xlink:href="options.html#opt-services.kanata.enable">services.kanata</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://github.com/aiberia/persistent-evdev">persistent-evdev</link>,
           a daemon to add virtual proxy devices that mirror a physical
           input device but persist even if the underlying hardware is
@@ -269,6 +277,13 @@
       </listitem>
       <listitem>
         <para>
+          The <literal>meta.mainProgram</literal> attribute of packages
+          in <literal>wineWowPackages</literal> now defaults to
+          <literal>&quot;wine64&quot;</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           (Neo)Vim can not be configured with
           <literal>configure.pathogen</literal> anymore to reduce
           maintainance burden. Use <literal>configure.packages</literal>
diff --git a/nixos/doc/manual/installation/building-nixos.chapter.md b/nixos/doc/manual/installation/building-nixos.chapter.md
index 27d7e1d385539..17da261fbdaa4 100644
--- a/nixos/doc/manual/installation/building-nixos.chapter.md
+++ b/nixos/doc/manual/installation/building-nixos.chapter.md
@@ -18,9 +18,12 @@ enforced values with `mkForce`.
 
 ## Practical Instructions {#sec-building-image-instructions}
 
+To build an ISO image for the channel `nixos-unstable`:
+
 ```ShellSession
 $ git clone https://github.com/NixOS/nixpkgs.git
 $ cd nixpkgs/nixos
+$ git switch nixos-unstable
 $ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix
 ```
 
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index 7e830f8e4583b..dd7f883bb3c30 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -303,7 +303,8 @@ Use the following commands:
 
     UEFI systems
 
-    :   You *must* set the option [](#opt-boot.loader.systemd-boot.enable)
+    :   You must select a boot-loader, either system-boot or GRUB. The recommended
+        option is systemd-boot: set the option [](#opt-boot.loader.systemd-boot.enable)
         to `true`. `nixos-generate-config` should do this automatically
         for new configurations when booted in UEFI mode.
 
@@ -312,6 +313,15 @@ Use the following commands:
         [`boot.loader.systemd-boot`](#opt-boot.loader.systemd-boot.enable)
         as well.
 
+    :   If you want to use GRUB, set [](#opt-boot.loader.grub.device) to `nodev` and
+        [](#opt-boot.loader.grub.efiSupport) to `true`.
+
+    :   With system-boot, you should not need any special configuration to detect
+        other installed systems. With GRUB, set [](#opt-boot.loader.grub.useOSProber)
+        to `true`, but this will only detect windows partitions, not other linux
+        distributions. If you dual boot another linux distribution, use system-boot
+        instead.
+
     If you need to configure networking for your machine the
     configuration options are described in [](#sec-networking). In
     particular, while wifi is supported on the installation image, it is
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index d4059e7393222..607da187b899c 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -58,6 +58,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
   Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+
+- [kanata](https://github.com/jtroo/kanata), a tool to improve keyboard comfort and usability with advanced customization.
+  Available as [services.kanata](options.html#opt-services.kanata.enable).
+
 - [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable).
 
 - [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
@@ -104,6 +108,8 @@ In addition to numerous new and upgraded packages, this release has the followin
   `python3.pkgs.influxgraph` packages, have been removed due to lack of upstream
   maintenance.
 
+- The `meta.mainProgram` attribute of packages in `wineWowPackages` now defaults to `"wine64"`.
+
 - (Neo)Vim can not be configured with `configure.pathogen` anymore to reduce maintainance burden.
 Use `configure.packages` instead.
 
diff --git a/nixos/lib/qemu-common.nix b/nixos/lib/qemu-common.nix
index 250f714be0a7a..fc3dcb24ab9c1 100644
--- a/nixos/lib/qemu-common.nix
+++ b/nixos/lib/qemu-common.nix
@@ -18,7 +18,7 @@ rec {
     ];
 
   qemuSerialDevice = if pkgs.stdenv.hostPlatform.isx86 || pkgs.stdenv.hostPlatform.isRiscV then "ttyS0"
-        else if (with pkgs.stdenv.hostPlatform; isAarch32 || isAarch64 || isPower) then "ttyAMA0"
+        else if (with pkgs.stdenv.hostPlatform; isAarch || isPower) then "ttyAMA0"
         else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
 
   qemuBinary = qemuPkg: {
diff --git a/nixos/modules/config/appstream.nix b/nixos/modules/config/appstream.nix
index a72215c2f5613..5b48f6e1705d9 100644
--- a/nixos/modules/config/appstream.nix
+++ b/nixos/modules/config/appstream.nix
@@ -6,9 +6,9 @@ with lib;
     appstream.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://www.freedesktop.org/software/appstream/docs/index.html">AppStream metadata specification</link>.
+        [AppStream metadata specification](https://www.freedesktop.org/software/appstream/docs/index.html).
       '';
     };
   };
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 97e6405db91e1..94c1c0514f9f8 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -159,7 +159,11 @@ in
           "${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
           "${config.boot.initrd.systemd.package.kbd}/bin/setfont"
           "${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
-          "${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # keyboard layouts are compressed
+          "${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
+        ] ++ optionals (hasPrefix builtins.storeDir cfg.font) [
+          "${cfg.font}"
+        ] ++ optionals (hasPrefix builtins.storeDir cfg.keyMap) [
+          "${cfg.keyMap}"
         ];
 
         systemd.services.reload-systemd-vconsole-setup =
diff --git a/nixos/modules/config/fonts/fontdir.nix b/nixos/modules/config/fonts/fontdir.nix
index 560918302ca66..30e0dfe2566a8 100644
--- a/nixos/modules/config/fonts/fontdir.nix
+++ b/nixos/modules/config/fonts/fontdir.nix
@@ -30,9 +30,9 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create a directory with links to all fonts in
-          <filename>/run/current-system/sw/share/X11/fonts</filename>.
+          {file}`/run/current-system/sw/share/X11/fonts`.
         '';
       };
 
@@ -40,9 +40,9 @@ in
         type = types.bool;
         default = config.programs.xwayland.enable;
         defaultText = literalExpression "config.programs.xwayland.enable";
-        description = ''
+        description = lib.mdDoc ''
           Whether to decompress fonts in
-          <filename>/run/current-system/sw/share/X11/fonts</filename>.
+          {file}`/run/current-system/sw/share/X11/fonts`.
         '';
       };
 
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index cdf5c661e5e9e..b78e678488530 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -57,6 +57,7 @@ with lib;
           (builtins.map (l: (replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") (
             [
               "C.UTF-8"
+              "en_US.UTF-8"
               config.i18n.defaultLocale
             ] ++ (attrValues (filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
           ));
diff --git a/nixos/modules/config/locale.nix b/nixos/modules/config/locale.nix
index 6f05658818774..7716e121c7129 100644
--- a/nixos/modules/config/locale.nix
+++ b/nixos/modules/config/locale.nix
@@ -22,9 +22,8 @@ in
         default = null;
         type = timezone;
         example = "America/New_York";
-        description = ''
-          The time zone used when displaying times and dates. See <link
-          xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"/>
+        description = lib.mdDoc ''
+          The time zone used when displaying times and dates. See <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
           for a comprehensive list of possible values for this setting.
 
           If null, the timezone will default to UTC and can be set imperatively
@@ -35,7 +34,7 @@ in
       hardwareClockInLocalTime = mkOption {
         default = false;
         type = types.bool;
-        description = "If set, keep the hardware clock in local time instead of UTC.";
+        description = lib.mdDoc "If set, keep the hardware clock in local time instead of UTC.";
       };
 
     };
@@ -44,18 +43,18 @@ in
 
       latitude = mkOption {
         type = types.float;
-        description = ''
+        description = lib.mdDoc ''
           Your current latitude, between
-          <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
+          `-90.0` and `90.0`. Must be provided
           along with longitude.
         '';
       };
 
       longitude = mkOption {
         type = types.float;
-        description = ''
+        description = lib.mdDoc ''
           Your current longitude, between
-          between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
+          between `-180.0` and `180.0`. Must be
           provided along with latitude.
         '';
       };
@@ -63,9 +62,9 @@ in
       provider = mkOption {
         type = types.enum [ "manual" "geoclue2" ];
         default = "manual";
-        description = ''
+        description = lib.mdDoc ''
           The location provider to use for determining your location. If set to
-          <literal>manual</literal> you must also provide latitude/longitude.
+          `manual` you must also provide latitude/longitude.
         '';
       };
 
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index e494ff5f74d5a..b004072813bdf 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -13,10 +13,10 @@ with lib;
       type = types.listOf types.path;
       internal = true;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Search path for NSS (Name Service Switch) modules.  This allows
         several DNS resolution methods to be specified via
-        <filename>/etc/nsswitch.conf</filename>.
+        {file}`/etc/nsswitch.conf`.
       '';
       apply = list:
         {
@@ -28,8 +28,8 @@ with lib;
     system.nssDatabases = {
       passwd = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of passwd entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of passwd entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
 
@@ -40,8 +40,8 @@ with lib;
 
       group = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of group entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of group entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
 
@@ -52,8 +52,8 @@ with lib;
 
       shadow = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of shadow entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of shadow entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended.
 
@@ -64,8 +64,8 @@ with lib;
 
       hosts = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of hosts entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of hosts entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended, and "dns" and "myhostname" are always appended.
 
@@ -76,8 +76,8 @@ with lib;
 
       services = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of services entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of services entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended.
 
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 01555d28b73fb..aa3ca549f09a9 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -89,7 +89,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the PulseAudio sound server.
         '';
       };
@@ -97,7 +97,7 @@ in {
       systemWide = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If false, a PulseAudio server is launched automatically for
           each user that tries to use the sound system. The server runs
           with user privileges. If true, one system-wide PulseAudio
@@ -112,7 +112,7 @@ in {
       support32Bit = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to include the 32-bit pulseaudio libraries in the system or not.
           This is only useful on 64-bit systems and currently limited to x86_64-linux.
         '';
@@ -120,7 +120,7 @@ in {
 
       configFile = mkOption {
         type = types.nullOr types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to the default configuration options the PulseAudio server
           should use. By default, the "default.pa" configuration
           from the PulseAudio distribution is used.
@@ -130,8 +130,8 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Literal string to append to <literal>configFile</literal>
+        description = lib.mdDoc ''
+          Literal string to append to `configFile`
           and the config file generated by the pulseaudio module.
         '';
       };
@@ -139,7 +139,7 @@ in {
       extraClientConf = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration appended to pulse/client.conf file.
         '';
       };
@@ -151,10 +151,10 @@ in {
                   else pkgs.pulseaudio;
         defaultText = literalExpression "pkgs.pulseaudio";
         example = literalExpression "pkgs.pulseaudioFull";
-        description = ''
+        description = lib.mdDoc ''
           The PulseAudio derivation to use.  This can be used to enable
           features (such as JACK support, Bluetooth) via the
-          <literal>pulseaudioFull</literal> package.
+          `pulseaudioFull` package.
         '';
       };
 
@@ -162,7 +162,7 @@ in {
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.pulseaudio-modules-bt ]";
-        description = ''
+        description = lib.mdDoc ''
           Extra pulseaudio modules to use. This is intended for out-of-tree
           pulseaudio modules like extra bluetooth codecs.
 
@@ -174,7 +174,7 @@ in {
         logLevel = mkOption {
           type = types.str;
           default = "notice";
-          description = ''
+          description = lib.mdDoc ''
             The log level that the system-wide pulseaudio daemon should use,
             if activated.
           '';
@@ -183,7 +183,7 @@ in {
         config = mkOption {
           type = types.attrsOf types.unspecified;
           default = {};
-          description = "Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.";
+          description = lib.mdDoc "Config of the pulse daemon. See `man pulse-daemon.conf`.";
           example = literalExpression ''{ realtime-scheduling = "yes"; }'';
         };
       };
@@ -205,7 +205,7 @@ in {
             type = types.listOf types.str;
             default = [];
             example = literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
-            description = ''
+            description = lib.mdDoc ''
               A list of IP subnets that are allowed to stream to the server.
             '';
           };
diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix
index 0cda1ca9c3ec3..9e19774b582fb 100644
--- a/nixos/modules/config/qt5.nix
+++ b/nixos/modules/config/qt5.nix
@@ -45,41 +45,17 @@ in
           ["lxqt" "lxqt-qtplugin"]
           ["libsForQt5" "plasma-integration"]
         ];
-        description = ''
-          Selects the platform theme to use for Qt5 applications.</para>
-          <para>The options are
-          <variablelist>
-            <varlistentry>
-              <term><literal>gtk</literal></term>
-              <listitem><para>Use GTK theme with
-                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>gnome</literal></term>
-              <listitem><para>Use GNOME theme with
-                <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>lxqt</literal></term>
-              <listitem><para>Use LXQt style set using the
-                <link xlink:href="https://github.com/lxqt/lxqt-config">lxqt-config-appearance</link>
-                application.
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>qt5ct</literal></term>
-              <listitem><para>Use Qt style set using the
-                <link xlink:href="https://sourceforge.net/projects/qt5ct/">qt5ct</link>
-                application.
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>kde</literal></term>
-              <listitem><para>Use Qt settings from Plasma.</para></listitem>
-            </varlistentry>
-          </variablelist>
+        description = lib.mdDoc ''
+          Selects the platform theme to use for Qt5 applications.
+
+          The options are
+          - `gtk`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `gnome`: Use GNOME theme with [qgnomeplatform](https://github.com/FedoraQt/QGnomePlatform)
+          - `lxqt`: Use LXQt style set using the [lxqt-config-appearance](https://github.com/lxqt/lxqt-config)
+             application.
+          - `qt5ct`: Use Qt style set using the [qt5ct](https://sourceforge.net/projects/qt5ct/)
+             application.
+          - `kde`: Use Qt settings from Plasma.
         '';
       };
 
@@ -97,27 +73,14 @@ in
           "adwaita-qt"
           ["libsForQt5" "qtstyleplugins"]
         ];
-        description = ''
-          Selects the style to use for Qt5 applications.</para>
-          <para>The options are
-          <variablelist>
-            <varlistentry>
-              <term><literal>adwaita</literal></term>
-              <term><literal>adwaita-dark</literal></term>
-              <listitem><para>Use Adwaita Qt style with
-                <link xlink:href="https://github.com/FedoraQt/adwaita-qt">adwaita</link>
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>cleanlooks</literal></term>
-              <term><literal>gtk2</literal></term>
-              <term><literal>motif</literal></term>
-              <term><literal>plastique</literal></term>
-              <listitem><para>Use styles from
-                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
-              </para></listitem>
-            </varlistentry>
-          </variablelist>
+        description = lib.mdDoc ''
+          Selects the style to use for Qt5 applications.
+
+          The options are
+          - `adwaita`, `adwaita-dark`: Use Adwaita Qt style with
+            [adwaita](https://github.com/FedoraQt/adwaita-qt)
+          - `cleanlooks`, `gtk2`, `motif`, `plastique`: Use styles from
+            [qtstyleplugins](https://github.com/qt/qtstyleplugins)
         '';
       };
     };
diff --git a/nixos/modules/config/unix-odbc-drivers.nix b/nixos/modules/config/unix-odbc-drivers.nix
index 055c3b2364e6e..7bd3fa1600b09 100644
--- a/nixos/modules/config/unix-odbc-drivers.nix
+++ b/nixos/modules/config/unix-odbc-drivers.nix
@@ -20,10 +20,10 @@ in {
       type = types.listOf types.package;
       default = [];
       example = literalExpression "with pkgs.unixODBCDrivers; [ sqlite psql ]";
-      description = ''
+      description = lib.mdDoc ''
         Specifies Unix ODBC drivers to be registered in
-        <filename>/etc/odbcinst.ini</filename>.  You may also want to
-        add <literal>pkgs.unixODBC</literal> to the system path to get
+        {file}`/etc/odbcinst.ini`.  You may also want to
+        add `pkgs.unixODBC` to the system path to get
         a command line client to connect to ODBC databases.
       '';
     };
diff --git a/nixos/modules/config/xdg/autostart.nix b/nixos/modules/config/xdg/autostart.nix
index 40984cb5ec53f..a4fdbda911a28 100644
--- a/nixos/modules/config/xdg/autostart.nix
+++ b/nixos/modules/config/xdg/autostart.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.autostart.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG Autostart specification</link>.
+        [XDG Autostart specification](https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html).
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/icons.nix b/nixos/modules/config/xdg/icons.nix
index 1e91670cf03bf..8d44a431445bc 100644
--- a/nixos/modules/config/xdg/icons.nix
+++ b/nixos/modules/config/xdg/icons.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.icons.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html">XDG Icon Theme specification</link>.
+        [XDG Icon Theme specification](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html).
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/menus.nix b/nixos/modules/config/xdg/menus.nix
index 6735a7a5c430f..b8f829e81547b 100644
--- a/nixos/modules/config/xdg/menus.nix
+++ b/nixos/modules/config/xdg/menus.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.menus.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html">XDG Desktop Menu specification</link>.
+        [XDG Desktop Menu specification](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html).
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/sounds.nix b/nixos/modules/config/xdg/sounds.nix
index 0b94f550929b4..713d68131fc0b 100644
--- a/nixos/modules/config/xdg/sounds.nix
+++ b/nixos/modules/config/xdg/sounds.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.sounds.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/">XDG Sound Theme specification</link>.
+        [XDG Sound Theme specification](https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/).
       '';
     };
   };
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 89a1217dfb313..bd5540646b086 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -62,7 +62,7 @@ in {
         alsa-firmware
         sof-firmware
         libreelec-dvb-firmware
-      ] ++ optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) raspberrypiWirelessFirmware
+      ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
       ] ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "5.16") [
diff --git a/nixos/modules/hardware/cpu/intel-sgx.nix b/nixos/modules/hardware/cpu/intel-sgx.nix
index 1355ee753f0d3..76664133a08ff 100644
--- a/nixos/modules/hardware/cpu/intel-sgx.nix
+++ b/nixos/modules/hardware/cpu/intel-sgx.nix
@@ -6,13 +6,13 @@ let
 in
 {
   options.hardware.cpu.intel.sgx.enableDcapCompat = mkOption {
-    description = ''
+    description = lib.mdDoc ''
       Whether to enable backward compatibility for SGX software build for the
       out-of-tree Intel SGX DCAP driver.
 
-      Creates symbolic links for the SGX devices <literal>/dev/sgx_enclave</literal>
-      and <literal>/dev/sgx_provision</literal> to make them available as
-      <literal>/dev/sgx/enclave</literal>  and <literal>/dev/sgx/provision</literal>,
+      Creates symbolic links for the SGX devices `/dev/sgx_enclave`
+      and `/dev/sgx_provision` to make them available as
+      `/dev/sgx/enclave`  and `/dev/sgx/provision`,
       respectively.
     '';
     type = types.bool;
@@ -22,17 +22,17 @@ in
   options.hardware.cpu.intel.sgx.provision = {
     enable = mkEnableOption "access to the Intel SGX provisioning device";
     user = mkOption {
-      description = "Owner to assign to the SGX provisioning device.";
+      description = lib.mdDoc "Owner to assign to the SGX provisioning device.";
       type = types.str;
       default = "root";
     };
     group = mkOption {
-      description = "Group to assign to the SGX provisioning device.";
+      description = lib.mdDoc "Group to assign to the SGX provisioning device.";
       type = types.str;
       default = defaultPrvGroup;
     };
     mode = mkOption {
-      description = "Mode to set for the SGX provisioning device.";
+      description = lib.mdDoc "Mode to set for the SGX provisioning device.";
       type = types.str;
       default = "0660";
     };
diff --git a/nixos/modules/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix
index 829c3532c4597..ba7a1c12169f2 100644
--- a/nixos/modules/hardware/ksm.nix
+++ b/nixos/modules/hardware/ksm.nix
@@ -15,9 +15,9 @@ in {
     sleep = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         How many milliseconds ksmd should sleep between scans.
-        Setting it to <literal>null</literal> uses the kernel's default time.
+        Setting it to `null` uses the kernel's default time.
       '';
     };
   };
diff --git a/nixos/modules/i18n/input-method/fcitx.nix b/nixos/modules/i18n/input-method/fcitx.nix
index 7738581b893a2..043ec3d55c1f6 100644
--- a/nixos/modules/i18n/input-method/fcitx.nix
+++ b/nixos/modules/i18n/input-method/fcitx.nix
@@ -22,9 +22,9 @@ in
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.fcitx-engines;
             engines = concatStringsSep ", "
-              (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+              (map (name: "`${name}`") (attrNames enginesDrv));
           in
-            "Enabled Fcitx engines. Available engines are: ${engines}.";
+            lib.mdDoc "Enabled Fcitx engines. Available engines are: ${engines}.";
       };
     };
 
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index 907f6451fce11..520db128acd9f 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -43,15 +43,15 @@ in
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines;
             engines = concatStringsSep ", "
-              (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+              (map (name: "`${name}`") (attrNames enginesDrv));
           in
-            "Enabled IBus engines. Available engines are: ${engines}.";
+            lib.mdDoc "Enabled IBus engines. Available engines are: ${engines}.";
       };
       panel = mkOption {
         type = with types; nullOr path;
         default = null;
         example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel"'';
-        description = "Replace the IBus panel with another panel.";
+        description = lib.mdDoc "Replace the IBus panel with another panel.";
       };
     };
   };
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index d1ccc6c2072f7..9309fe70a8618 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -476,9 +476,9 @@ in
 
     isoImage.squashfsCompression = mkOption {
       default = with pkgs.stdenv.targetPlatform; "xz -Xdict-size 100% "
-                + lib.optionalString (isx86_32 || isx86_64) "-Xbcj x86"
+                + lib.optionalString isx86 "-Xbcj x86"
                 # Untested but should also reduce size for these platforms
-                + lib.optionalString (isAarch32 || isAarch64) "-Xbcj arm"
+                + lib.optionalString isAarch "-Xbcj arm"
                 + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
                 + lib.optionalString (isSparc) "-Xbcj sparc";
       description = ''
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 8719a509d64c1..4490ad84e1447 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -75,15 +75,15 @@ in
     configuration = mkOption {
       internal = true;
       type = types.str;
-      description = ''
-        The NixOS module that <literal>nixos-generate-config</literal>
-        saves to <literal>/etc/nixos/configuration.nix</literal>.
+      description = lib.mdDoc ''
+        The NixOS module that `nixos-generate-config`
+        saves to `/etc/nixos/configuration.nix`.
 
         This is an internal option. No backward compatibility is guaranteed.
         Use at your own risk!
 
         Note that this string gets spliced into a Perl script. The perl
-        variable <literal>$bootLoaderConfig</literal> can be used to
+        variable `$bootLoaderConfig` can be used to
         splice in the boot loader configuration.
       '';
     };
@@ -92,15 +92,15 @@ in
       internal = true;
       type = types.listOf types.lines;
       default = [];
-      description = ''
-        Text to preseed the desktop configuration that <literal>nixos-generate-config</literal>
-        saves to <literal>/etc/nixos/configuration.nix</literal>.
+      description = lib.mdDoc ''
+        Text to preseed the desktop configuration that `nixos-generate-config`
+        saves to `/etc/nixos/configuration.nix`.
 
         This is an internal option. No backward compatibility is guaranteed.
         Use at your own risk!
 
         Note that this string gets spliced into a Perl script. The perl
-        variable <literal>$bootLoaderConfig</literal> can be used to
+        variable `$bootLoaderConfig` can be used to
         splice in the boot loader configuration.
       '';
     };
@@ -110,7 +110,7 @@ in
     internal = true;
     type = types.bool;
     default = false;
-    description = ''
+    description = lib.mdDoc ''
       Disable nixos-rebuild, nixos-generate-config, nixos-installer
       and other NixOS tools. This is useful to shrink embedded,
       read-only systems which are not expected to be rebuild or
diff --git a/nixos/modules/misc/man-db.nix b/nixos/modules/misc/man-db.nix
index 7aeb02d883ac8..d267ad125646e 100644
--- a/nixos/modules/misc/man-db.nix
+++ b/nixos/modules/misc/man-db.nix
@@ -36,8 +36,8 @@ in
         type = lib.types.package;
         default = pkgs.man-db;
         defaultText = lib.literalExpression "pkgs.man-db";
-        description = ''
-          The <literal>man-db</literal> derivation to use. Useful to override
+        description = lib.mdDoc ''
+          The `man-db` derivation to use. Useful to override
           configuration options used for the package.
         '';
       };
diff --git a/nixos/modules/misc/mandoc.nix b/nixos/modules/misc/mandoc.nix
index 838f20876563a..d67c42bff6a09 100644
--- a/nixos/modules/misc/mandoc.nix
+++ b/nixos/modules/misc/mandoc.nix
@@ -16,9 +16,9 @@ in {
         type = with lib.types; listOf str;
         default = [ "share/man" ];
         example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]";
-        description = ''
+        description = lib.mdDoc ''
           Change the manpath, i. e. the directories where
-          <citerefentry><refentrytitle>man</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+          {manpage}`man(1)`
           looks for section-specific directories of man pages.
           You only need to change this setting if you want extra man pages
           (e. g. in non-english languages). All values must be strings that
@@ -31,8 +31,8 @@ in {
         type = lib.types.package;
         default = pkgs.mandoc;
         defaultText = lib.literalExpression "pkgs.mandoc";
-        description = ''
-          The <literal>mandoc</literal> derivation to use. Useful to override
+        description = lib.mdDoc ''
+          The `mandoc` derivation to use. Useful to override
           configuration options used for the package.
         '';
       };
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index da458a5748401..bdc3e5623be68 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -49,42 +49,42 @@ in
     nixos.version = mkOption {
       internal = true;
       type = types.str;
-      description = "The full NixOS version (e.g. <literal>16.03.1160.f2d4ee1</literal>).";
+      description = lib.mdDoc "The full NixOS version (e.g. `16.03.1160.f2d4ee1`).";
     };
 
     nixos.release = mkOption {
       readOnly = true;
       type = types.str;
       default = trivial.release;
-      description = "The NixOS release (e.g. <literal>16.03</literal>).";
+      description = lib.mdDoc "The NixOS release (e.g. `16.03`).";
     };
 
     nixos.versionSuffix = mkOption {
       internal = true;
       type = types.str;
       default = trivial.versionSuffix;
-      description = "The NixOS version suffix (e.g. <literal>1160.f2d4ee1</literal>).";
+      description = lib.mdDoc "The NixOS version suffix (e.g. `1160.f2d4ee1`).";
     };
 
     nixos.revision = mkOption {
       internal = true;
       type = types.nullOr types.str;
       default = trivial.revisionWithDefault null;
-      description = "The Git revision from which this NixOS configuration was built.";
+      description = lib.mdDoc "The Git revision from which this NixOS configuration was built.";
     };
 
     nixos.codeName = mkOption {
       readOnly = true;
       type = types.str;
       default = trivial.codeName;
-      description = "The NixOS release code name (e.g. <literal>Emu</literal>).";
+      description = lib.mdDoc "The NixOS release code name (e.g. `Emu`).";
     };
 
     stateVersion = mkOption {
       type = types.str;
       default = cfg.release;
       defaultText = literalExpression "config.${opt.release}";
-      description = ''
+      description = lib.mdDoc ''
         Every once in a while, a new NixOS release may change
         configuration defaults in a way incompatible with stateful
         data. For instance, if the default version of PostgreSQL
@@ -108,13 +108,13 @@ in
       internal = true;
       type = types.str;
       default = "https://nixos.org/channels/nixos-unstable";
-      description = "Default NixOS channel to which the root user is subscribed.";
+      description = lib.mdDoc "Default NixOS channel to which the root user is subscribed.";
     };
 
     configurationRevision = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "The Git revision of the top-level flake from which this configuration was built.";
+      description = lib.mdDoc "The Git revision of the top-level flake from which this configuration was built.";
     };
 
   };
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3010a213705b2..d961e2f683bd5 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -447,6 +447,7 @@
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/joycond.nix
+  ./services/hardware/kanata.nix
   ./services/hardware/lcd.nix
   ./services/hardware/lirc.nix
   ./services/hardware/nvidia-optimus.nix
diff --git a/nixos/modules/profiles/all-hardware.nix b/nixos/modules/profiles/all-hardware.nix
index 8347453d403b4..af1e3d32a0a29 100644
--- a/nixos/modules/profiles/all-hardware.nix
+++ b/nixos/modules/profiles/all-hardware.nix
@@ -57,7 +57,7 @@ in
 
       # Hyper-V support.
       "hv_storvsc"
-    ] ++ lib.optionals (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+    ] ++ lib.optionals pkgs.stdenv.hostPlatform.isAarch [
       # Most of the following falls into two categories:
       #  - early KMS / early display
       #  - early storage (e.g. USB) support
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index a31078a891a0c..a0763d2dcf6b0 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -20,7 +20,7 @@ in
         type = types.package;
         default = pkgs.atop;
         defaultText = literalExpression "pkgs.atop";
-        description = ''
+        description = lib.mdDoc ''
           Which package to use for Atop.
         '';
       };
@@ -29,7 +29,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to install and enable the netatop kernel module.
             Note: this sets the kernel taint flag "O" for loading out-of-tree modules.
           '';
@@ -38,7 +38,7 @@ in
           type = types.package;
           default = config.boot.kernelPackages.netatop;
           defaultText = literalExpression "config.boot.kernelPackages.netatop";
-          description = ''
+          description = lib.mdDoc ''
             Which package to use for netatop.
           '';
         };
@@ -47,7 +47,7 @@ in
       atopgpu.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install and enable the atopgpud daemon to get information about
           NVIDIA gpus.
         '';
@@ -56,7 +56,7 @@ in
       setuidWrapper.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install a setuid wrapper for Atop. This is required to use some of
           the features as non-root user (e.g.: ipc information, netatop, atopgpu).
           Atop tries to drop the root privileges shortly after starting.
@@ -66,7 +66,7 @@ in
       atopService.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atop service responsible for storing statistics for
           long-term analysis.
         '';
@@ -74,7 +74,7 @@ in
       atopRotateTimer.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atop-rotate timer, which restarts the atop service
           daily to make sure the data files are rotate.
         '';
@@ -82,7 +82,7 @@ in
       atopacctService.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atopacct service which manages process accounting.
           This allows Atop to gather data about processes that disappeared in between
           two refresh intervals.
@@ -95,8 +95,8 @@ in
           flags = "a1f";
           interval = 5;
         };
-        description = ''
-          Parameters to be written to <filename>/etc/atoprc</filename>.
+        description = lib.mdDoc ''
+          Parameters to be written to {file}`/etc/atoprc`.
         '';
       };
     };
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 7281126979e50..249e99ddc4721 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -30,10 +30,10 @@ in
       /*
       enable = mkOption {
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whenever to configure Bash as an interactive shell.
           Note that this tries to make Bash the default
-          <option>users.defaultUserShell</option>,
+          {option}`users.defaultUserShell`,
           which in turn means that you might need to explicitly
           set this variable if you have another shell configured
           with NixOS.
@@ -44,16 +44,16 @@ in
 
       shellAliases = mkOption {
         default = {};
-        description = ''
-          Set of aliases for bash shell, which overrides <option>environment.shellAliases</option>.
-          See <option>environment.shellAliases</option> for an option format description.
+        description = lib.mdDoc ''
+          Set of aliases for bash shell, which overrides {option}`environment.shellAliases`.
+          See {option}`environment.shellAliases` for an option format description.
         '';
         type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during bash shell initialisation.
         '';
         type = types.lines;
@@ -61,7 +61,7 @@ in
 
       loginShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during login bash shell initialisation.
         '';
         type = types.lines;
@@ -69,7 +69,7 @@ in
 
       interactiveShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during interactive bash shell initialisation.
         '';
         type = types.lines;
@@ -92,7 +92,7 @@ in
             fi
           fi
         '';
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise the bash prompt.
         '';
         type = types.lines;
@@ -100,7 +100,7 @@ in
 
       promptPluginInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise bash prompt plugins.
         '';
         type = types.lines;
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index 265c41cbbbc99..b5ef42a3b72bc 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -33,14 +33,14 @@ in
       profiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Set of dconf profile files, installed at <filename>/etc/dconf/profiles/<replaceable>name</replaceable></filename>.";
+        description = lib.mdDoc "Set of dconf profile files, installed at {file}`/etc/dconf/profiles/«name»`.";
         internal = true;
       };
 
       packages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = "A list of packages which provide dconf profiles and databases in <filename>/etc/dconf</filename>.";
+        description = lib.mdDoc "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`.";
       };
     };
   };
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 8dd7101947fae..357105c3e79b3 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -49,7 +49,7 @@ in
 
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure fish as an interactive shell.
         '';
         type = types.bool;
@@ -58,16 +58,16 @@ in
       useBabelfish = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If enabled, the configured environment will be translated to native fish using <link xlink:href="https://github.com/bouk/babelfish">babelfish</link>.
-          Otherwise, <link xlink:href="https://github.com/oh-my-fish/plugin-foreign-env">foreign-env</link> will be used.
+        description = lib.mdDoc ''
+          If enabled, the configured environment will be translated to native fish using [babelfish](https://github.com/bouk/babelfish).
+          Otherwise, [foreign-env](https://github.com/oh-my-fish/plugin-foreign-env) will be used.
         '';
       };
 
       vendor.config.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should source configuration snippets provided by other packages.
         '';
       };
@@ -75,7 +75,7 @@ in
       vendor.completions.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should use completion files provided by other packages.
         '';
       };
@@ -83,7 +83,7 @@ in
       vendor.functions.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should autoload fish functions provided by other packages.
         '';
       };
@@ -94,7 +94,7 @@ in
           gco = "git checkout";
           npu = "nix-prefetch-url";
         };
-        description = ''
+        description = lib.mdDoc ''
           Set of fish abbreviations.
         '';
         type = with types; attrsOf str;
@@ -102,16 +102,16 @@ in
 
       shellAliases = mkOption {
         default = {};
-        description = ''
-          Set of aliases for fish shell, which overrides <option>environment.shellAliases</option>.
-          See <option>environment.shellAliases</option> for an option format description.
+        description = lib.mdDoc ''
+          Set of aliases for fish shell, which overrides {option}`environment.shellAliases`.
+          See {option}`environment.shellAliases` for an option format description.
         '';
         type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during fish shell initialisation.
         '';
         type = types.lines;
@@ -119,7 +119,7 @@ in
 
       loginShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during fish login shell initialisation.
         '';
         type = types.lines;
@@ -127,7 +127,7 @@ in
 
       interactiveShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during interactive fish shell initialisation.
         '';
         type = types.lines;
@@ -135,7 +135,7 @@ in
 
       promptInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise fish prompt.
         '';
         type = types.lines;
diff --git a/nixos/modules/programs/less.nix b/nixos/modules/programs/less.nix
index 794146b19faf5..9f2d5d9158159 100644
--- a/nixos/modules/programs/less.nix
+++ b/nixos/modules/programs/less.nix
@@ -41,12 +41,12 @@ in
         type = types.nullOr types.path;
         default = null;
         example = literalExpression ''"''${pkgs.my-configs}/lesskey"'';
-        description = ''
+        description = lib.mdDoc ''
           Path to lesskey configuration file.
 
-          <option>configFile</option> takes precedence over <option>commands</option>,
-          <option>clearDefaultCommands</option>, <option>lineEditingKeys</option>, and
-          <option>envVariables</option>.
+          {option}`configFile` takes precedence over {option}`commands`,
+          {option}`clearDefaultCommands`, {option}`lineEditingKeys`, and
+          {option}`envVariables`.
         '';
       };
 
@@ -57,13 +57,13 @@ in
           h = "noaction 5\\e(";
           l = "noaction 5\\e)";
         };
-        description = "Defines new command keys.";
+        description = lib.mdDoc "Defines new command keys.";
       };
 
       clearDefaultCommands = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Clear all default commands.
           You should remember to set the quit key.
           Otherwise you will not be able to leave less without killing it.
@@ -76,7 +76,7 @@ in
         example = {
           e = "abort";
         };
-        description = "Defines new line-editing keys.";
+        description = lib.mdDoc "Defines new line-editing keys.";
       };
 
       envVariables = mkOption {
@@ -87,14 +87,14 @@ in
         example = {
           LESS = "--quit-if-one-screen";
         };
-        description = "Defines environment variables.";
+        description = lib.mdDoc "Defines environment variables.";
       };
 
       lessopen = mkOption {
         type = types.nullOr types.str;
         default = "|${pkgs.lesspipe}/bin/lesspipe.sh %s";
         defaultText = literalExpression ''"|''${pkgs.lesspipe}/bin/lesspipe.sh %s"'';
-        description = ''
+        description = lib.mdDoc ''
           Before less opens a file, it first gives your input preprocessor a chance to modify the way the contents of the file are displayed.
         '';
       };
@@ -102,7 +102,7 @@ in
       lessclose = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           When less closes a file opened in such a way, it will call another program, called the input postprocessor, which may  perform  any  desired  clean-up  action (such  as deleting the replacement file created by LESSOPEN).
         '';
       };
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 5837dd46d7cd7..16bab620d6e2a 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -14,9 +14,9 @@ in
       nanorc = lib.mkOption {
         type = lib.types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The system-wide nano configuration.
-          See <citerefentry><refentrytitle>nanorc</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+          See {manpage}`nanorc(5)`.
         '';
         example = ''
           set nowrap
@@ -27,7 +27,7 @@ in
       syntaxHighlight = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = "Whether to enable syntax highlight for various languages.";
+        description = lib.mdDoc "Whether to enable syntax highlight for various languages.";
       };
     };
   };
diff --git a/nixos/modules/programs/proxychains.nix b/nixos/modules/programs/proxychains.nix
index 3f44e23a93efe..5d932b2d84238 100644
--- a/nixos/modules/programs/proxychains.nix
+++ b/nixos/modules/programs/proxychains.nix
@@ -26,17 +26,17 @@ let
 
       type = mkOption {
         type = types.enum [ "http" "socks4" "socks5" ];
-        description = "Proxy type.";
+        description = lib.mdDoc "Proxy type.";
       };
 
       host = mkOption {
         type = types.str;
-        description = "Proxy host or IP address.";
+        description = lib.mdDoc "Proxy host or IP address.";
       };
 
       port = mkOption {
         type = types.port;
-        description = "Proxy port";
+        description = lib.mdDoc "Proxy port";
       };
     };
   };
@@ -55,26 +55,26 @@ in {
         type = mkOption {
           type = types.enum [ "dynamic" "strict" "random" ];
           default = "strict";
-          description = ''
-            <literal>dynamic</literal> - Each connection will be done via chained proxies
+          description = lib.mdDoc ''
+            `dynamic` - Each connection will be done via chained proxies
             all proxies chained in the order as they appear in the list
             at least one proxy must be online to play in chain
             (dead proxies are skipped)
-            otherwise <literal>EINTR</literal> is returned to the app.
+            otherwise `EINTR` is returned to the app.
 
-            <literal>strict</literal> - Each connection will be done via chained proxies
+            `strict` - Each connection will be done via chained proxies
             all proxies chained in the order as they appear in the list
             all proxies must be online to play in chain
-            otherwise <literal>EINTR</literal> is returned to the app.
+            otherwise `EINTR` is returned to the app.
 
-            <literal>random</literal> - Each connection will be done via random proxy
-            (or proxy chain, see <option>programs.proxychains.chain.length</option>) from the list.
+            `random` - Each connection will be done via random proxy
+            (or proxy chain, see {option}`programs.proxychains.chain.length`) from the list.
           '';
         };
         length = mkOption {
           type = types.nullOr types.int;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Chain length for random chain.
           '';
         };
@@ -83,7 +83,7 @@ in {
       proxyDNS = mkOption {
         type = types.bool;
         default = true;
-        description = "Proxy DNS requests - no leak for DNS data.";
+        description = lib.mdDoc "Proxy DNS requests - no leak for DNS data.";
       };
 
       quietMode = mkEnableOption "Quiet mode (no output from the library).";
@@ -91,7 +91,7 @@ in {
       remoteDNSSubnet = mkOption {
         type = types.enum [ 10 127 224 ];
         default = 224;
-        description = ''
+        description = lib.mdDoc ''
           Set the class A subnet number to use for the internal remote DNS mapping, uses the reserved 224.x.x.x range by default.
         '';
       };
@@ -99,24 +99,24 @@ in {
       tcpReadTimeOut = mkOption {
         type = types.int;
         default = 15000;
-        description = "Connection read time-out in milliseconds.";
+        description = lib.mdDoc "Connection read time-out in milliseconds.";
       };
 
       tcpConnectTimeOut = mkOption {
         type = types.int;
         default = 8000;
-        description = "Connection time-out in milliseconds.";
+        description = lib.mdDoc "Connection time-out in milliseconds.";
       };
 
       localnet = mkOption {
         type = types.str;
         default = "127.0.0.0/255.0.0.0";
-        description = "By default enable localnet for loopback address ranges.";
+        description = lib.mdDoc "By default enable localnet for loopback address ranges.";
       };
 
       proxies = mkOption {
         type = types.attrsOf (types.submodule proxyOptions);
-        description = ''
+        description = lib.mdDoc ''
           Proxies to be used by proxychains.
         '';
 
diff --git a/nixos/modules/programs/spacefm.nix b/nixos/modules/programs/spacefm.nix
index f71abcaa33253..b4ba9dcdea567 100644
--- a/nixos/modules/programs/spacefm.nix
+++ b/nixos/modules/programs/spacefm.nix
@@ -17,8 +17,8 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to install SpaceFM and create <filename>/etc/spacefm/spacefm.conf</filename>.
+        description = lib.mdDoc ''
+          Whether to install SpaceFM and create {file}`/etc/spacefm/spacefm.conf`.
         '';
       };
 
@@ -34,10 +34,10 @@ in
             terminal_su = "''${pkgs.sudo}/bin/sudo";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           The system-wide spacefm configuration.
-          Parameters to be written to <filename>/etc/spacefm/spacefm.conf</filename>.
-          Refer to the <link xlink:href="https://ignorantguru.github.io/spacefm/spacefm-manual-en.html#programfiles-etc">relevant entry</link> in the SpaceFM manual.
+          Parameters to be written to {file}`/etc/spacefm/spacefm.conf`.
+          Refer to the [relevant entry](https://ignorantguru.github.io/spacefm/spacefm-manual-en.html#programfiles-etc) in the SpaceFM manual.
         '';
       };
 
diff --git a/nixos/modules/programs/starship.nix b/nixos/modules/programs/starship.nix
index 83d2272003c63..ade80b9999e18 100644
--- a/nixos/modules/programs/starship.nix
+++ b/nixos/modules/programs/starship.nix
@@ -16,8 +16,8 @@ in {
     settings = mkOption {
       inherit (settingsFormat) type;
       default = { };
-      description = ''
-        Configuration included in <literal>starship.toml</literal>.
+      description = lib.mdDoc ''
+        Configuration included in `starship.toml`.
 
         See https://starship.rs/config/#prompt for documentation.
       '';
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index 065587bc286e6..674ba857818ca 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -11,11 +11,11 @@ with lib;
     security.lockKernelModules = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Disable kernel module loading once the system is fully initialised.
         Module loading is disabled until the next reboot. Problems caused
         by delayed module loading can be fixed by adding the module(s) in
-        question to <option>boot.kernelModules</option>.
+        question to {option}`boot.kernelModules`.
       '';
     };
   };
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index a80312367d856..d9d072b36e6e6 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -482,10 +482,10 @@ let
           (let p11 = config.security.pam.p11; in optionalString cfg.p11Auth ''
             auth ${p11.control} ${pkgs.pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so
           '') +
-          (let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth ''
+          (let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth (''
               auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ''
                 + ''${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"} ${optionalString (u2f.appId != null) "appid=${u2f.appId}"} ${optionalString (u2f.origin != null) "origin=${u2f.origin}"}
-          '') +
+          '')) +
           optionalString cfg.usbAuth ''
             auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
           '' +
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index 1ba149745c654..0a2d81445ba53 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -29,7 +29,7 @@ in
             if (subject.local) return "yes";
           });
         '';
-      description =
+      description = lib.mdDoc
         ''
           Any polkit rules to be added to config (in JavaScript ;-). See:
           http://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules
@@ -40,12 +40,12 @@ in
       type = types.listOf types.str;
       default = [ "unix-group:wheel" ];
       example = [ "unix-user:alice" "unix-group:admin" ];
-      description =
+      description = lib.mdDoc
         ''
           Specifies which users are considered “administrators”, for those
           actions that require the user to authenticate as an
-          administrator (i.e. have an <literal>auth_admin</literal>
-          value).  By default, this is all users in the <literal>wheel</literal> group.
+          administrator (i.e. have an `auth_admin`
+          value).  By default, this is all users in the `wheel` group.
         '';
     };
 
diff --git a/nixos/modules/services/audio/hqplayerd.nix b/nixos/modules/services/audio/hqplayerd.nix
index 416d12ce21724..822a8abef2439 100644
--- a/nixos/modules/services/audio/hqplayerd.nix
+++ b/nixos/modules/services/audio/hqplayerd.nix
@@ -133,7 +133,7 @@ in
     users.users = {
       hqplayer = {
         description = "hqplayer daemon user";
-        extraGroups = [ "audio" ];
+        extraGroups = [ "audio" "video" ];
         group = "hqplayer";
         uid = config.ids.uids.hqplayer;
       };
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
index de1f61c8e73b7..c8a2071c15592 100644
--- a/nixos/modules/services/audio/roon-server.nix
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -53,10 +53,12 @@ in {
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPortRanges = [
         { from = 9100; to = 9200; }
-        { from = 9330; to = 9332; }
+        { from = 9330; to = 9339; }
+        { from = 30000; to = 30010; }
       ];
       allowedUDPPorts = [ 9003 ];
       extraCommands = ''
+        ## IGMP / Broadcast ##
         iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
         iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
         iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 333fdd494e3b9..a0de1124c661c 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -222,6 +222,15 @@ in
             A script that must run after finishing the backup process.
           '';
         };
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.restic;
+          defaultText = literalExpression "pkgs.restic";
+          description = ''
+            Restic package to use.
+          '';
+        };
       };
     }));
     default = { };
@@ -254,7 +263,7 @@ in
         (name: backup:
           let
             extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
-            resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
+            resticCmd = "${backup.package}/bin/restic${extraOptions}";
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
             backupPaths =
               if (backup.dynamicFilesFrom == null)
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 85ac0fb2a8907..03d3d2d16e3b0 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -22,6 +22,14 @@ let
       export CONFIG_FILE=${configPath}
 
       mkdir -p $(dirname ${configPath})
+      touch ${configPath}
+
+      # update global options
+      remarshal --if toml --of json ${configPath} \
+        | jq -cM 'with_entries(select([.key] | inside(["runners"])))' \
+        | jq -scM '.[0] + .[1]' - <(echo ${escapeShellArg (toJSON cfg.settings)}) \
+        | remarshal --if json --of toml \
+        | sponge ${configPath}
 
       # remove no longer existing services
       gitlab-runner verify --delete
@@ -91,22 +99,6 @@ let
           --name "$NAME" && sleep 1
       done
 
-      # update global options
-      remarshal --if toml --of json ${configPath} \
-        | jq -cM ${escapeShellArg (concatStringsSep " | " [
-            ".check_interval = ${toJSON cfg.checkInterval}"
-            ".concurrent = ${toJSON cfg.concurrent}"
-            ".sentry_dsn = ${toJSON cfg.sentryDSN}"
-            ".listen_address = ${toJSON cfg.prometheusListenAddress}"
-            ".session_server.listen_address = ${toJSON cfg.sessionServer.listenAddress}"
-            ".session_server.advertise_address = ${toJSON cfg.sessionServer.advertiseAddress}"
-            ".session_server.session_timeout = ${toJSON cfg.sessionServer.sessionTimeout}"
-            "del(.[] | nulls)"
-            "del(.session_server[] | nulls)"
-          ])} \
-        | remarshal --if json --of toml \
-        | sponge ${configPath}
-
       # make config file readable by service
       chown -R --reference=$HOME $(dirname ${configPath})
     '');
@@ -133,85 +125,15 @@ in
         for settings not covered by this module.
       '';
     };
-    checkInterval = mkOption {
-      type = types.int;
-      default = 0;
-      example = literalExpression "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
-      description = ''
-        Defines the interval length, in seconds, between new jobs check.
-        The default value is 3;
-        if set to 0 or lower, the default value will be used.
-        See <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works">runner documentation</link> for more information.
-      '';
-    };
-    concurrent = mkOption {
-      type = types.int;
-      default = 1;
-      example = literalExpression "config.nix.settings.max-jobs";
-      description = ''
-        Limits how many jobs globally can be run concurrently.
-        The most upper limit of jobs using all defined runners.
-        0 does not mean unlimited.
-      '';
-    };
-    sentryDSN = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "https://public:private@host:port/1";
-      description = ''
-        Data Source Name for tracking of all system level errors to Sentry.
-      '';
-    };
-    prometheusListenAddress = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "localhost:8080";
-      description = ''
-        Address (&lt;host&gt;:&lt;port&gt;) on which the Prometheus metrics HTTP server
-        should be listening.
-      '';
-    };
-    sessionServer = mkOption {
+    settings = mkOption {
       type = types.submodule {
-        options = {
-          listenAddress = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "0.0.0.0:8093";
-            description = ''
-              An internal URL to be used for the session server.
-            '';
-          };
-          advertiseAddress = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "runner-host-name.tld:8093";
-            description = ''
-              The URL that the Runner will expose to GitLab to be used
-              to access the session server.
-              Fallbacks to <option>listenAddress</option> if not defined.
-            '';
-          };
-          sessionTimeout = mkOption {
-            type = types.int;
-            default = 1800;
-            description = ''
-              How long in seconds the session can stay active after
-              the job completes (which will block the job from finishing).
-            '';
-          };
-        };
+        freeformType = (pkgs.formats.json { }).type;
       };
       default = { };
-      example = literalExpression ''
-        {
-          listenAddress = "0.0.0.0:8093";
-        }
-      '';
       description = ''
-        The session server allows the user to interact with jobs
-        that the Runner is responsible for. A good example of this is the
-        <link xlink:href="https://docs.gitlab.com/ee/ci/interactive_web_terminal/index.html">interactive web terminal</link>.
+        Global gitlab-runner configuration. See
+        <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section"/>
+        for supported values.
       '';
     };
     gracefulTermination = mkOption {
@@ -535,8 +457,8 @@ in
   config = mkIf cfg.enable {
     warnings = (mapAttrsToList
       (n: v: "services.gitlab-runner.services.${n}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.")
-      (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services))
-    ++ optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`.";
+      (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services));
+
     environment.systemPackages = [ cfg.package ];
     systemd.services.gitlab-runner = {
       description = "Gitlab Runner";
@@ -584,5 +506,14 @@ in
     (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
     (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
     (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
+
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "checkInterval" ] [ "services" "gitlab-runner" "settings" "check_interval" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "concurrent" ] [ "services" "gitlab-runner" "settings" "concurrent" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sentryDSN" ] [ "services" "gitlab-runner" "settings" "sentry_dsn" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "prometheusListenAddress" ] [ "services" "gitlab-runner" "settings" "listen_address" ] )
+
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "listenAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "listen_address" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "advertiseAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "advertise_address" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "sessionTimeout" ] [ "services" "gitlab-runner" "settings" "session_server" "session_timeout" ] )
   ];
 }
diff --git a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index edbf31f5ca1a3..49b39b03d4758 100644
--- a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -243,6 +243,7 @@ in {
             done
           '' + (if cfg.accessUser != "" then reloadScript else "");
       serviceConfig = {
+        Type = "oneshot";
         User = jenkinsCfg.user;
         RuntimeDirectory = "jenkins-job-builder";
       };
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
new file mode 100644
index 0000000000000..f8250afa4a000
--- /dev/null
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kanata;
+
+  keyboard = {
+    options = {
+      device = mkOption {
+        type = types.str;
+        example = "/dev/input/by-id/usb-0000_0000-event-kbd";
+        description = "Path to the keyboard device.";
+      };
+      config = mkOption {
+        type = types.lines;
+        example = ''
+          (defsrc
+            grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
+            tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
+            caps a    s    d    f    g    h    j    k    l    ;    '    ret
+            lsft z    x    c    v    b    n    m    ,    .    /    rsft
+            lctl lmet lalt           spc            ralt rmet rctl)
+
+          (deflayer qwerty
+            grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
+            tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
+            @cap a    s    d    f    g    h    j    k    l    ;    '    ret
+            lsft z    x    c    v    b    n    m    ,    .    /    rsft
+            lctl lmet lalt           spc            ralt rmet rctl)
+
+          (defalias
+            ;; tap within 100ms for capslk, hold more than 100ms for lctl
+            cap (tap-hold 100 100 caps lctl))
+        '';
+        description = ''
+          Configuration other than defcfg.
+          See <link xlink:href="https://github.com/jtroo/kanata"/> for more information.
+        '';
+      };
+      extraDefCfg = mkOption {
+        type = types.lines;
+        default = "";
+        example = "danger-enable-cmd yes";
+        description = ''
+          Configuration of defcfg other than linux-dev.
+          See <link xlink:href="https://github.com/jtroo/kanata"/> for more information.
+        '';
+      };
+    };
+  };
+
+  mkName = name: "kanata-${name}";
+
+  mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
+    (defcfg
+      ${keyboard.extraDefCfg}
+      linux-dev ${keyboard.device})
+
+    ${keyboard.config}
+  '';
+
+  mkService = name: keyboard: nameValuePair (mkName name) {
+    description = "kanata for ${keyboard.device}";
+
+    # Because path units are used to activate service units, which
+    # will start the old stopped services during "nixos-rebuild
+    # switch", stopIfChanged here is a workaround to make sure new
+    # services are running after "nixos-rebuild switch".
+    stopIfChanged = false;
+
+    serviceConfig = {
+      ExecStart = ''
+        ${cfg.package}/bin/kanata \
+          --cfg ${mkConfig name keyboard}
+      '';
+
+      DynamicUser = true;
+      SupplementaryGroups = with config.users.groups; [
+        input.name
+        uinput.name
+      ];
+
+      # hardening
+      DeviceAllow = [
+        "/dev/uinput w"
+        "char-input r"
+      ];
+      CapabilityBoundingSet = "";
+      DevicePolicy = "closed";
+      IPAddressDeny = "any";
+      LockPersonality = true;
+      MemoryDenyWriteExecute = true;
+      PrivateNetwork = true;
+      PrivateUsers = true;
+      ProcSubset = "pid";
+      ProtectClock = true;
+      ProtectControlGroups = true;
+      ProtectHome = true;
+      ProtectHostname = true;
+      ProtectKernelLogs = true;
+      ProtectKernelModules = true;
+      ProtectKernelTunables = true;
+      ProtectProc = "invisible";
+      RestrictAddressFamilies = "none";
+      RestrictNamespaces = true;
+      RestrictRealtime = true;
+      SystemCallArchitectures = "native";
+      SystemCallFilter = [
+        "@system-service"
+        "~@privileged"
+        "~@resources"
+      ];
+      UMask = "0077";
+    };
+  };
+
+  mkPath = name: keyboard: nameValuePair (mkName name) {
+    description = "kanata trigger for ${keyboard.device}";
+    wantedBy = [ "multi-user.target" ];
+    pathConfig = {
+      PathExists = keyboard.device;
+    };
+  };
+in
+{
+  options.services.kanata = {
+    enable = mkEnableOption "kanata";
+    package = mkOption {
+      type = types.package;
+      default = pkgs.kanata;
+      defaultText = lib.literalExpression "pkgs.kanata";
+      example = lib.literalExpression "pkgs.kanata-with-cmd";
+      description = ''
+        kanata package to use.
+        If you enable danger-enable-cmd, pkgs.kanata-with-cmd should be used.
+      '';
+    };
+    keyboards = mkOption {
+      type = types.attrsOf (types.submodule keyboard);
+      default = { };
+      description = "Keyboard configurations.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    hardware.uinput.enable = true;
+
+    systemd = {
+      paths = mapAttrs' mkPath cfg.keyboards;
+      services = mapAttrs' mkService cfg.keyboards;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ linj ];
+}
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 2cff5051c757f..1d76d2fd39c68 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -92,7 +92,7 @@ in {
         "default_config"
         "met"
         "esphome"
-      ] ++ optionals (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) [
+      ] ++ optionals pkgs.stdenv.hostPlatform.isAarch [
         # Use the platform as an indicator that we might be running on a RaspberryPi and include
         # relevant components
         "rpi_power"
diff --git a/nixos/modules/services/matrix/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 794c4dd9ddcd7..88e5c38f71633 100644
--- a/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -125,6 +125,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+      path = [ pkgs.lottieconverter ];
 
       preStart = ''
         # Not all secrets can be passed as environment variable (yet)
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index effa0c06ad6c5..81ec0d7c47e1b 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -506,24 +506,24 @@ in
           function gitea_setup {
             cp -f ${configFile} ${runConfig}
 
-            if [ ! -e ${secretKey} ]; then
+            if [ ! -s ${secretKey} ]; then
                 ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
             fi
 
             # Migrate LFS_JWT_SECRET filename
-            if [[ -e ${oldLfsJwtSecret} && ! -e ${lfsJwtSecret} ]]; then
+            if [[ -s ${oldLfsJwtSecret} && ! -s ${lfsJwtSecret} ]]; then
                 mv ${oldLfsJwtSecret} ${lfsJwtSecret}
             fi
 
-            if [ ! -e ${oauth2JwtSecret} ]; then
+            if [ ! -s ${oauth2JwtSecret} ]; then
                 ${gitea}/bin/gitea generate secret JWT_SECRET > ${oauth2JwtSecret}
             fi
 
-            if [ ! -e ${lfsJwtSecret} ]; then
+            if [ ! -s ${lfsJwtSecret} ]; then
                 ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
             fi
 
-            if [ ! -e ${internalToken} ]; then
+            if [ ! -s ${internalToken} ]; then
                 ${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
             fi
 
diff --git a/nixos/modules/services/monitoring/grafana-agent.nix b/nixos/modules/services/monitoring/grafana-agent.nix
index bbeda18464707..a4b18b4f28e9a 100644
--- a/nixos/modules/services/monitoring/grafana-agent.nix
+++ b/nixos/modules/services/monitoring/grafana-agent.nix
@@ -48,9 +48,10 @@ in
         freeformType = settingsFormat.type;
       };
 
-      default = {
+      default = { };
+      defaultText = ''
         metrics = {
-          wal_directory = "\${STATE_DIRECTORY}";
+          wal_directory = "\''${STATE_DIRECTORY}";
           global.scrape_interval = "5s";
         };
         integrations = {
@@ -59,8 +60,7 @@ in
           node_exporter.enabled = true;
           replace_instance_label = true;
         };
-      };
-
+      '';
       example = {
         metrics.global.remote_write = [{
           url = "\${METRICS_REMOTE_WRITE_URL}";
@@ -104,6 +104,20 @@ in
   };
 
   config = mkIf cfg.enable {
+    services.grafana-agent.settings = {
+      # keep this in sync with config.services.grafana-agent.settings.defaultText.
+      metrics = {
+        wal_directory = mkDefault "\${STATE_DIRECTORY}";
+        global.scrape_interval = mkDefault "5s";
+      };
+      integrations = {
+        agent.enabled = mkDefault true;
+        agent.scrape_integration = mkDefault true;
+        node_exporter.enabled = mkDefault true;
+        replace_instance_label = mkDefault true;
+      };
+    };
+
     systemd.services.grafana-agent = {
       wantedBy = [ "multi-user.target" ];
       script = ''
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 03a76cab9f5ee..baf869af1c428 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -186,7 +186,7 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ curl gawk iproute2 which procps ])
+      path = (with pkgs; [ curl gawk iproute2 which procps bash ])
         ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
         ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package);
       environment = {
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 5b07beadb45f8..a9958c884da8d 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -429,12 +429,16 @@ in
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ configFile ];
 
+      environment.GIN_MODE = "release";
+
       script = ''
         ${optionalString (cfg.database.passwordFile != null) ''
           export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.database.passwordFile})"
         ''}
 
-        export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
+        ${optionalString (cfg.openIdConnect.clientSecretFile != null) ''
+          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
+        ''}
         exec ${cfg.package}/bin/headscale serve
       '';
 
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 06ec04dbbf16a..84b9936aa6235 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -59,6 +59,14 @@ in
         description = "If enabled, start the Murmur Mumble server.";
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Murmur Mumble server.
+        '';
+      };
+
       autobanAttempts = mkOption {
         type = types.int;
         default = 10;
@@ -291,6 +299,11 @@ in
       gid             = config.ids.gids.murmur;
     };
 
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+      allowedUDPPorts = [ cfg.port ];
+    };
+
     systemd.services.murmur = {
       description = "Murmur Chat Service";
       wantedBy    = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 6b69d559748c5..52a50b892ec61 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -442,7 +442,9 @@ in
 
                 ${flip concatMapStrings cfg.hostKeys (k: ''
                   if ! [ -s "${k.path}" ]; then
-                      rm -f "${k.path}"
+                      if ! [ -h "${k.path}" ]; then
+                          rm -f "${k.path}"
+                      fi
                       ssh-keygen \
                         -t "${k.type}" \
                         ${if k ? bits then "-b ${toString k.bits}" else ""} \
diff --git a/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index df4908a0fff9e..55fac27f92ce9 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -7,80 +7,27 @@ let
   cfg = config.services.stunnel;
   yesNo = val: if val then "yes" else "no";
 
+  verifyRequiredField = type: field: n: c: {
+    assertion = hasAttr field c;
+    message =  "stunnel: \"${n}\" ${type} configuration - Field ${field} is required.";
+  };
+
   verifyChainPathAssert = n: c: {
-    assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
+    assertion = (c.verifyHostname or null) == null || (c.verifyChain || c.verifyPeer);
     message =  "stunnel: \"${n}\" client configuration - hostname verification " +
       "is not possible without either verifyChain or verifyPeer enabled";
   };
 
-  serverConfig = {
-    options = {
-      accept = mkOption {
-        type = types.either types.str types.int;
-        description = ''
-          On which [host:]port stunnel should listen for incoming TLS connections.
-          Note that unlike other softwares stunnel ipv6 address need no brackets,
-          so to listen on all IPv6 addresses on port 1234 one would use ':::1234'.
-        '';
-      };
-
-      connect = mkOption {
-        type = types.either types.str types.int;
-        description = "Port or IP:Port to which the decrypted connection should be forwarded.";
-      };
-
-      cert = mkOption {
-        type = types.path;
-        description = "File containing both the private and public keys.";
-      };
-    };
-  };
-
-  clientConfig = {
-    options = {
-      accept = mkOption {
-        type = types.str;
-        description = "IP:Port on which connections should be accepted.";
-      };
-
-      connect = mkOption {
-        type = types.str;
-        description = "IP:Port destination to connect to.";
-      };
-
-      verifyChain = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
-      };
-
-      verifyPeer = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Check if the provided certificate is contained in CAPath.";
-      };
-
-      CAPath = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = "Path to a directory containing certificates to validate against.";
-      };
-
-      CAFile = mkOption {
-        type = types.nullOr types.path;
-        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
-        defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
-        description = "Path to a file containing certificates to validate against.";
-      };
-
-      verifyHostname = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
-      };
-    };
-  };
-
+  removeNulls = mapAttrs (_: filterAttrs (_: v: v != null));
+  mkValueString = v:
+    if v == true then "yes"
+    else if v == false then "no"
+    else generators.mkValueStringDefault {} v;
+  generateConfig = c:
+    generators.toINI {
+      mkSectionName = id;
+      mkKeyValue = k: v: "${k} = ${mkValueString v}";
+    } (removeNulls c);
 
 in
 
@@ -130,8 +77,13 @@ in
 
 
       servers = mkOption {
-        description = "Define the server configuations.";
-        type = with types; attrsOf (submodule serverConfig);
+        description = ''
+          Define the server configuations.
+
+          See "SERVICE-LEVEL OPTIONS" in <citerefentry><refentrytitle>stunnel</refentrytitle>
+          <manvolnum>8</manvolnum></citerefentry>.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
         example = {
           fancyWebserver = {
             accept = 443;
@@ -143,8 +95,33 @@ in
       };
 
       clients = mkOption {
-        description = "Define the client configurations.";
-        type = with types; attrsOf (submodule clientConfig);
+        description = ''
+          Define the client configurations.
+
+          By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert.
+
+          See "SERVICE-LEVEL OPTIONS" in <citerefentry><refentrytitle>stunnel</refentrytitle>
+          <manvolnum>8</manvolnum></citerefentry>.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
+
+        apply = let
+          applyDefaults = c:
+            {
+              CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+              OCSPaia = true;
+              verifyChain = true;
+            } // c;
+          setCheckHostFromVerifyHostname = c:
+            # To preserve backward-compatibility with the old NixOS stunnel module
+            # definition, allow "verifyHostname" as an alias for "checkHost".
+            c // {
+              checkHost = c.checkHost or c.verifyHostname or null;
+              verifyHostname = null; # Not a real stunnel configuration setting
+            };
+          forceClient = c: c // { client = true; };
+        in mapAttrs (_: c: forceClient (setCheckHostFromVerifyHostname (applyDefaults c)));
+
         example = {
           foobar = {
             accept = "0.0.0.0:8080";
@@ -169,6 +146,11 @@ in
       })
 
       (mapAttrsToList verifyChainPathAssert cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "accept") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "connect") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "server" "accept") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "cert") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "connect") cfg.servers)
     ];
 
     environment.systemPackages = [ pkgs.stunnel ];
@@ -183,36 +165,10 @@ in
       ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
 
       ; ----- SERVER CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               accept = ${toString v.accept}
-               connect = ${toString v.connect}
-               cert = ${v.cert}
-
-             '')
-           cfg.servers)
-      }
+      ${ generateConfig cfg.servers }
 
       ; ----- CLIENT CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               client = yes
-               accept = ${v.accept}
-               connect = ${v.connect}
-               verifyChain = ${yesNo v.verifyChain}
-               verifyPeer = ${yesNo v.verifyPeer}
-               ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
-               ${optionalString (v.CAFile != null) "CAFile = ${v.CAFile}"}
-               ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
-               OCSPaia = yes
-
-             '')
-           cfg.clients)
-      }
+      ${ generateConfig cfg.clients }
     '';
 
     systemd.services.stunnel = {
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 0f697c0cc2555..66b85cd9d8a9f 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -30,15 +30,22 @@ let
   updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
     set -efu
 
+    # be careful not to leak secrets in the filesystem or in process listings
+
+    umask 0077
+
     # get the api key by parsing the config.xml
     while
-        ! api_key=$(${pkgs.libxml2}/bin/xmllint \
+        ! ${pkgs.libxml2}/bin/xmllint \
             --xpath 'string(configuration/gui/apikey)' \
-            ${cfg.configDir}/config.xml)
+            ${cfg.configDir}/config.xml \
+            >"$RUNTIME_DIRECTORY/api_key"
     do sleep 1; done
 
+    (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
+
     curl() {
-        ${pkgs.curl}/bin/curl -sSLk -H "X-API-Key: $api_key" \
+        ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
             --retry 1000 --retry-delay 1 --retry-all-errors \
             "$@"
     }
@@ -576,6 +583,7 @@ in {
         serviceConfig = {
           User = cfg.user;
           RemainAfterExit = true;
+          RuntimeDirectory = "syncthing-init";
           Type = "oneshot";
           ExecStart = updateConfig;
         };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index 79306541b855b..9418aff12adb3 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -14,6 +14,21 @@ let
     proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}";
   });
 
+  crowdPropertiesFile = pkgs.writeText "crowd.properties" ''
+    application.name                        crowd-openid-server
+    application.password @NIXOS_CROWD_OPENID_PW@
+    application.base.url                    http://localhost:${toString cfg.listenPort}/openidserver
+    application.login.url                   http://localhost:${toString cfg.listenPort}/openidserver
+    application.login.url.template          http://localhost:${toString cfg.listenPort}/openidserver?returnToUrl=''${RETURN_TO_URL}
+
+    crowd.server.url                        http://localhost:${toString cfg.listenPort}/crowd/services/
+
+    session.isauthenticated                 session.isauthenticated
+    session.tokenkey                        session.tokenkey
+    session.validationinterval              0
+    session.lastvalidation                  session.lastvalidation
+  '';
+
 in
 
 {
@@ -53,9 +68,16 @@ in
 
       openidPassword = mkOption {
         type = types.str;
+        default = "WILL_NEVER_BE_SET";
         description = "Application password for OpenID server.";
       };
 
+      openidPasswordFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Path to the file containing the application password for OpenID server.";
+      };
+
       catalinaOptions = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -140,6 +162,7 @@ in
         JAVA_HOME = "${cfg.jrePackage}";
         CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
         CATALINA_TMPDIR = "/tmp";
+        JAVA_OPTS = mkIf (cfg.openidPasswordFile != null) "-Dcrowd.properties=${cfg.home}/crowd.properties";
       };
 
       preStart = ''
@@ -151,6 +174,14 @@ in
           -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
         '') + ''
           ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
+
+        ${optionalString (cfg.openidPasswordFile != null) ''
+          install -m660 ${crowdPropertiesFile} ${cfg.home}/crowd.properties
+          ${pkgs.replace-secret}/bin/replace-secret \
+            '@NIXOS_CROWD_OPENID_PW@' \
+            ${cfg.openidPasswordFile} \
+            ${cfg.home}/crowd.properties
+        ''}
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index 909b51750d857..f0e4f753eb70d 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -843,7 +843,8 @@ in
               '';
             };
             searchAttributes = mkOption {
-              type = types.listOf types.str;
+              type = types.nullOr (types.listOf types.str);
+              default = null;
               example = [ "displayName" "mail" ];
               description = ''
                 LDAP attributes to search with.
@@ -866,6 +867,7 @@ in
             };
             tlsca = mkOption {
               type = types.str;
+              default = "/etc/ssl/certs/ca-certificates.crt";
               example = "server-cert.pem,root.pem";
               description = ''
                 Root CA for LDAP TLS in PEM format.
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 2130ec252d92c..f8082d7544c12 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -6,6 +6,8 @@ let
   cfg = config.services.nextcloud;
   fpm = config.services.phpfpm.pools.nextcloud;
 
+  jsonFormat = pkgs.formats.json {};
+
   inherit (cfg) datadir;
 
   phpPackage = cfg.phpPackage.buildEnv {
@@ -547,6 +549,33 @@ in {
       '';
     };
 
+    extraOptions = mkOption {
+      type = jsonFormat.type;
+      default = {};
+      description = ''
+        Extra options which should be appended to nextcloud's config.php file.
+      '';
+      example = literalExpression '' {
+        redis = {
+          host = "/run/redis/redis.sock";
+          port = 0;
+          dbindex = 0;
+          password = "secret";
+          timeout = 1.5;
+        };
+      } '';
+    };
+
+    secretFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same
+        form as the <xref linkend="opt-services.nextcloud.extraOptions"/> option), for example
+        <programlisting>{"redis":{"password":"secret"}}</programlisting>.
+      '';
+    };
+
     nginx = {
       recommendedHttpHeaders = mkOption {
         type = types.bool;
@@ -706,10 +735,20 @@ in {
                     $file
                   ));
                 }
-
                 return trim(file_get_contents($file));
+              }''}
+            function nix_decode_json_file($file, $error) {
+              if (!file_exists($file)) {
+                throw new \RuntimeException(sprintf($error, $file));
               }
-            ''}
+              $decoded = json_decode(file_get_contents($file), true);
+
+              if (json_last_error() !== JSON_ERROR_NONE) {
+                throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
+              }
+
+              return $decoded;
+            }
             $CONFIG = [
               'apps_paths' => [
                 ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"}
@@ -728,7 +767,12 @@ in {
               ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
               ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
               ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
-              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('${c.dbpassFile}'),"}
+              ${optionalString (c.dbpassFile != null) ''
+                  'dbpassword' => nix_read_secret(
+                    "${c.dbpassFile}"
+                  ),
+                ''
+              }
               'dbtype' => '${c.dbtype}',
               'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
               'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
@@ -736,6 +780,18 @@ in {
               ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"}
               ${objectstoreConfig}
             ];
+
+            $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
+              "${jsonFormat.generate "nextcloud-extraOptions.json" cfg.extraOptions}",
+              "impossible: this should never happen (decoding generated extraOptions file %s failed)"
+            ));
+
+            ${optionalString (cfg.secretFile != null) ''
+              $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
+                "${cfg.secretFile}",
+                "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
+              ));
+            ''}
           '';
           occInstallCmd = let
             mkExport = { arg, value }: "export ${arg}=${value}";
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index a9929297a2480..61eef9f7ac96e 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -60,7 +60,7 @@ with lib;
         Note: This option overrides <literal>enableIPv6</literal>
       '';
       default = [];
-      example = [ "127.0.0.1" "::1" ];
+      example = [ "127.0.0.1" "[::1]" ];
     };
 
     enableACME = mkOption {
diff --git a/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix
index d71738ea633f2..4e56b393e2e56 100644
--- a/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixos/modules/services/x11/window-managers/default.nix
@@ -19,7 +19,8 @@ in
     ./evilwm.nix
     ./exwm.nix
     ./fluxbox.nix
-    ./fvwm.nix
+    ./fvwm2.nix
+    ./fvwm3.nix
     ./herbstluftwm.nix
     ./i3.nix
     ./jwm.nix
diff --git a/nixos/modules/services/x11/window-managers/fvwm.nix b/nixos/modules/services/x11/window-managers/fvwm.nix
deleted file mode 100644
index e283886ecc400..0000000000000
--- a/nixos/modules/services/x11/window-managers/fvwm.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.xserver.windowManager.fvwm;
-  fvwm = pkgs.fvwm.override { enableGestures = cfg.gestures; };
-in
-
-{
-
-  ###### interface
-
-  options = {
-    services.xserver.windowManager.fvwm = {
-      enable = mkEnableOption "Fvwm window manager";
-
-      gestures = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether or not to enable libstroke for gesture support";
-      };
-    };
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    services.xserver.windowManager.session = singleton
-      { name = "fvwm";
-        start =
-          ''
-            ${fvwm}/bin/fvwm &
-            waitPID=$!
-          '';
-      };
-
-    environment.systemPackages = [ fvwm ];
-  };
-}
diff --git a/nixos/modules/services/x11/window-managers/fvwm2.nix b/nixos/modules/services/x11/window-managers/fvwm2.nix
new file mode 100644
index 0000000000000..909b3a475a9c2
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/fvwm2.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fvwm2;
+  fvwm2 = pkgs.fvwm2.override { enableGestures = cfg.gestures; };
+in
+
+{
+
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "xserver" "windowManager" "fvwm" ]
+      [ "services" "xserver" "windowManager" "fvwm2" ])
+  ];
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.fvwm2 = {
+      enable = mkEnableOption "Fvwm2 window manager";
+
+      gestures = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether or not to enable libstroke for gesture support";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "fvwm2";
+        start =
+          ''
+            ${fvwm2}/bin/fvwm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ fvwm2 ];
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/fvwm3.nix b/nixos/modules/services/x11/window-managers/fvwm3.nix
new file mode 100644
index 0000000000000..43111f917d494
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/fvwm3.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fvwm3;
+  inherit (pkgs) fvwm3;
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.fvwm3 = {
+      enable = mkEnableOption "Fvwm3 window manager";
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "fvwm3";
+        start =
+          ''
+            ${fvwm3}/bin/fvwm3 &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ fvwm3 ];
+  };
+}
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index 1437ab3877009..5ace5dd06fd43 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -22,11 +22,11 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create symlinks to the system generations under
-          <literal>/boot</literal>.  When enabled,
-          <literal>/boot/default/kernel</literal>,
-          <literal>/boot/default/initrd</literal>, etc., are updated to
+          `/boot`.  When enabled,
+          `/boot/default/kernel`,
+          `/boot/default/initrd`, etc., are updated to
           point to the current generation's kernel image, initial RAM
           disk, and other bootstrap files.
 
@@ -41,7 +41,7 @@ in
       copyKernels = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether copy the necessary boot files into /boot, so
           /nix/store is not needed by the boot loader.
         '';
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 0336930b3ab7d..357c2bab993b5 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -452,6 +452,7 @@ let
           "AllMulticast"
           "Unmanaged"
           "RequiredForOnline"
+          "RequiredFamilyForOnline"
           "ActivationPolicy"
         ])
         (assertMacAddress "MACAddress")
@@ -471,6 +472,12 @@ let
           "enslaved"
           "routable"
         ]))
+        (assertValueOneOf "RequiredFamilyForOnline" [
+          "ipv4"
+          "ipv6"
+          "both"
+          "any"
+        ])
         (assertValueOneOf "ActivationPolicy" ([
           "up"
           "always-up"
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index 3a38201ff60d6..0ab2a875975d6 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -15,7 +15,7 @@ in
     services.resolved.enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the systemd DNS resolver daemon.
       '';
     };
@@ -24,7 +24,7 @@ in
       default = [ ];
       example = [ "8.8.8.8" "2001:4860:4860::8844" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of IPv4 and IPv6 addresses to use as the fallback DNS servers.
         If this option is empty, a compiled-in list of DNS servers is used instead.
       '';
@@ -35,7 +35,7 @@ in
       defaultText = literalExpression "config.networking.search";
       example = [ "example.com" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of domains. These domains are used as search suffixes
         when resolving single-label host names (domain names which
         contain no dot), in order to qualify them into fully-qualified
@@ -43,7 +43,7 @@ in
 
         For compatibility reasons, if this setting is not specified,
         the search domains listed in
-        <filename>/etc/resolv.conf</filename> are used instead, if
+        {file}`/etc/resolv.conf` are used instead, if
         that file exists and any domains are configured in it.
       '';
     };
@@ -52,32 +52,14 @@ in
       default = "true";
       example = "false";
       type = types.enum [ "true" "resolve" "false" ];
-      description = ''
+      description = lib.mdDoc ''
         Controls Link-Local Multicast Name Resolution support
         (RFC 4795) on the local host.
 
         If set to
-
-        <variablelist>
-        <varlistentry>
-          <term><literal>"true"</literal></term>
-          <listitem><para>
-            Enables full LLMNR responder and resolver support.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"false"</literal></term>
-          <listitem><para>
-            Disables both.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"resolve"</literal></term>
-          <listitem><para>
-            Only resolution support is enabled, but responding is disabled.
-          </para></listitem>
-        </varlistentry>
-        </variablelist>
+        - `"true"`: Enables full LLMNR responder and resolver support.
+        - `"false"`: Disables both.
+        - `"resolve"`: Only resolution support is enabled, but responding is disabled.
       '';
     };
 
@@ -85,21 +67,14 @@ in
       default = "allow-downgrade";
       example = "true";
       type = types.enum [ "true" "allow-downgrade" "false" ];
-      description = ''
+      description = lib.mdDoc ''
         If set to
-        <variablelist>
-        <varlistentry>
-          <term><literal>"true"</literal></term>
-          <listitem><para>
+        - `"true"`:
             all DNS lookups are DNSSEC-validated locally (excluding
             LLMNR and Multicast DNS). Note that this mode requires a
             DNS server that supports DNSSEC. If the DNS server does
             not properly support DNSSEC all validations will fail.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"allow-downgrade"</literal></term>
-          <listitem><para>
+        - `"allow-downgrade"`:
             DNSSEC validation is attempted, but if the server does not
             support DNSSEC properly, DNSSEC mode is automatically
             disabled. Note that this mode makes DNSSEC validation
@@ -107,22 +82,14 @@ in
             be able to trigger a downgrade to non-DNSSEC mode by
             synthesizing a DNS response that suggests DNSSEC was not
             supported.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"false"</literal></term>
-          <listitem><para>
-            DNS lookups are not DNSSEC validated.
-          </para></listitem>
-        </varlistentry>
-        </variablelist>
+        - `"false"`: DNS lookups are not DNSSEC validated.
       '';
     };
 
     services.resolved.extraConfig = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Extra config to append to resolved.conf.
       '';
     };
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 645fbc2b713a9..c7b62871eb4ef 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -177,11 +177,11 @@ in
       default = pkgs.systemd;
       defaultText = literalExpression "pkgs.systemd";
       type = types.package;
-      description = "The systemd package.";
+      description = lib.mdDoc "The systemd package.";
     };
 
     systemd.units = mkOption {
-      description = "Definition of systemd units.";
+      description = lib.mdDoc "Definition of systemd units.";
       default = {};
       type = systemdUtils.types.units;
     };
@@ -190,43 +190,43 @@ in
       default = [];
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = "Packages providing systemd units and hooks.";
+      description = lib.mdDoc "Packages providing systemd units and hooks.";
     };
 
     systemd.targets = mkOption {
       default = {};
       type = systemdUtils.types.targets;
-      description = "Definition of systemd target units.";
+      description = lib.mdDoc "Definition of systemd target units.";
     };
 
     systemd.services = mkOption {
       default = {};
       type = systemdUtils.types.services;
-      description = "Definition of systemd service units.";
+      description = lib.mdDoc "Definition of systemd service units.";
     };
 
     systemd.sockets = mkOption {
       default = {};
       type = systemdUtils.types.sockets;
-      description = "Definition of systemd socket units.";
+      description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     systemd.timers = mkOption {
       default = {};
       type = systemdUtils.types.timers;
-      description = "Definition of systemd timer units.";
+      description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     systemd.paths = mkOption {
       default = {};
       type = systemdUtils.types.paths;
-      description = "Definition of systemd path units.";
+      description = lib.mdDoc "Definition of systemd path units.";
     };
 
     systemd.mounts = mkOption {
       default = [];
       type = systemdUtils.types.mounts;
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
         the 'where' attribute.
@@ -236,7 +236,7 @@ in
     systemd.automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
         the 'where' attribute.
@@ -246,41 +246,41 @@ in
     systemd.slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      description = "Definition of slice configurations.";
+      description = lib.mdDoc "Definition of slice configurations.";
     };
 
     systemd.generators = mkOption {
       type = types.attrsOf types.path;
       default = {};
       example = { systemd-gpt-auto-generator = "/dev/null"; };
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd generators.
-        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
-        <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
+        For each `NAME = VALUE` pair of the attrSet, a link is generated from
+        `/etc/systemd/system-generators/NAME` to `VALUE`.
       '';
     };
 
     systemd.shutdown = mkOption {
       type = types.attrsOf types.path;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd shutdown executables.
-        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
-        <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
+        For each `NAME = VALUE` pair of the attrSet, a link is generated from
+        `/etc/systemd/system-shutdown/NAME` to `VALUE`.
       '';
     };
 
     systemd.defaultUnit = mkOption {
       default = "multi-user.target";
       type = types.str;
-      description = "Default unit started when the system boots.";
+      description = lib.mdDoc "Default unit started when the system boots.";
     };
 
     systemd.ctrlAltDelUnit = mkOption {
       default = "reboot.target";
       type = types.str;
       example = "poweroff.target";
-      description = ''
+      description = lib.mdDoc ''
         Target that should be started when Ctrl-Alt-Delete is pressed.
       '';
     };
@@ -289,8 +289,8 @@ in
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { TZ = "CET"; };
-      description = ''
-        Environment variables passed to <emphasis>all</emphasis> systemd units.
+      description = lib.mdDoc ''
+        Environment variables passed to *all* systemd units.
       '';
     };
 
@@ -298,16 +298,16 @@ in
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { SYSTEMD_LOG_LEVEL = "debug"; };
-      description = ''
+      description = lib.mdDoc ''
         Environment variables of PID 1. These variables are
-        <emphasis>not</emphasis> passed to started units.
+        *not* passed to started units.
       '';
     };
 
     systemd.enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable cgroup accounting.
       '';
     };
@@ -315,7 +315,7 @@ in
     systemd.enableUnifiedCgroupHierarchy = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the unified cgroup hierarchy (cgroupsv2).
       '';
     };
@@ -324,7 +324,7 @@ in
       default = "";
       type = types.lines;
       example = "DefaultLimitCORE=infinity";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd. See man systemd-system.conf for
         available options.
       '';
@@ -334,7 +334,7 @@ in
       default = "";
       type = types.lines;
       example = "HibernateDelaySec=1h";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd sleep state logic.
         See sleep.conf.d(5) man page for available options.
       '';
@@ -344,7 +344,7 @@ in
       default = [ ];
       type = types.listOf types.str;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
@@ -353,10 +353,10 @@ in
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of units to skip when generating system systemd configuration directory. This has
-        priority over upstream units, <option>systemd.units</option>, and
-        <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
+        priority over upstream units, {option}`systemd.units`, and
+        {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to
         prevent a upstream systemd unit from being added to the initrd with any modifications made to it
         by other NixOS modules.
       '';
@@ -366,7 +366,7 @@ in
       type = types.nullOr types.path;
       default = null;
       example = "/dev/watchdog";
-      description = ''
+      description = lib.mdDoc ''
         The path to a hardware watchdog device which will be managed by systemd.
         If not specified, systemd will default to /dev/watchdog.
       '';
@@ -376,7 +376,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "30s";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse before a watchdog hardware device
         will automatically reboot the system. Valid time units include "ms",
         "s", "min", "h", "d", and "w".
@@ -387,7 +387,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse after a reboot has been triggered
         before a watchdog hardware device will automatically reboot the system.
         Valid time units include "ms", "s", "min", "h", "d", and "w".
@@ -398,7 +398,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse when kexec is being executed before
         a watchdog hardware device will automatically reboot the system. This
         option should only be enabled if reloadTime is also enabled. Valid
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 5f93a8ac3c734..88e2bf9ac7043 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -150,7 +150,7 @@ in {
     };
 
     contents = mkOption {
-      description = "Set of files that have to be linked into the initrd";
+      description = lib.mdDoc "Set of files that have to be linked into the initrd";
       example = literalExpression ''
         {
           "/etc/hostname".text = "mymachine";
@@ -162,7 +162,7 @@ in {
     };
 
     storePaths = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Store paths to copy into the initrd as well.
       '';
       type = with types; listOf (oneOf [ singleLineStr package ]);
@@ -170,7 +170,7 @@ in {
     };
 
     extraBin = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Tools to add to /bin
       '';
       example = literalExpression ''
@@ -183,7 +183,7 @@ in {
     };
 
     suppressedStorePaths = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Store paths specified in the storePaths option that
         should not be copied.
       '';
@@ -194,7 +194,7 @@ in {
     emergencyAccess = mkOption {
       type = with types; oneOf [ bool (nullOr (passwdEntry str)) ];
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Set to true for unauthenticated emergency access, and false for
         no emergency access.
 
@@ -208,7 +208,7 @@ in {
       type = types.listOf types.package;
       default = [];
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Packages to include in /bin for the stage 1 emergency shell.
       '';
     };
@@ -218,7 +218,7 @@ in {
       type = types.listOf types.str;
       visible = false;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
@@ -228,17 +228,17 @@ in {
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         A list of units to skip when generating system systemd configuration directory. This has
-        priority over upstream units, <option>boot.initrd.systemd.units</option>, and
-        <option>boot.initrd.systemd.additionalUpstreamUnits</option>. The main purpose of this is to
+        priority over upstream units, {option}`boot.initrd.systemd.units`, and
+        {option}`boot.initrd.systemd.additionalUpstreamUnits`. The main purpose of this is to
         prevent a upstream systemd unit from being added to the initrd with any modifications made to it
         by other NixOS modules.
       '';
     };
 
     units = mkOption {
-      description = "Definition of systemd units.";
+      description = lib.mdDoc "Definition of systemd units.";
       default = {};
       visible = false;
       type = systemdUtils.types.units;
@@ -249,49 +249,49 @@ in {
       visible = false;
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = "Packages providing systemd units and hooks.";
+      description = lib.mdDoc "Packages providing systemd units and hooks.";
     };
 
     targets = mkOption {
       default = {};
       visible = false;
       type = systemdUtils.types.initrdTargets;
-      description = "Definition of systemd target units.";
+      description = lib.mdDoc "Definition of systemd target units.";
     };
 
     services = mkOption {
       default = {};
       type = systemdUtils.types.initrdServices;
       visible = false;
-      description = "Definition of systemd service units.";
+      description = lib.mdDoc "Definition of systemd service units.";
     };
 
     sockets = mkOption {
       default = {};
       type = systemdUtils.types.initrdSockets;
       visible = false;
-      description = "Definition of systemd socket units.";
+      description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     timers = mkOption {
       default = {};
       type = systemdUtils.types.initrdTimers;
       visible = false;
-      description = "Definition of systemd timer units.";
+      description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     paths = mkOption {
       default = {};
       type = systemdUtils.types.initrdPaths;
       visible = false;
-      description = "Definition of systemd path units.";
+      description = lib.mdDoc "Definition of systemd path units.";
     };
 
     mounts = mkOption {
       default = [];
       type = systemdUtils.types.initrdMounts;
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
         the 'where' attribute.
@@ -302,7 +302,7 @@ in {
       default = [];
       type = systemdUtils.types.automounts;
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
         the 'where' attribute.
@@ -313,7 +313,7 @@ in {
       default = {};
       type = systemdUtils.types.slices;
       visible = false;
-      description = "Definition of slice configurations.";
+      description = lib.mdDoc "Definition of slice configurations.";
     };
   };
 
diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix
index cf6d19eb5f0ec..1f9431710aec4 100644
--- a/nixos/modules/system/boot/tmp.nix
+++ b/nixos/modules/system/boot/tmp.nix
@@ -14,23 +14,23 @@ in
     boot.cleanTmpDir = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to delete all files in <filename>/tmp</filename> during boot.
+      description = lib.mdDoc ''
+        Whether to delete all files in {file}`/tmp` during boot.
       '';
     };
 
     boot.tmpOnTmpfs = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-         Whether to mount a tmpfs on <filename>/tmp</filename> during boot.
+      description = lib.mdDoc ''
+         Whether to mount a tmpfs on {file}`/tmp` during boot.
       '';
     };
 
     boot.tmpOnTmpfsSize = mkOption {
       type = types.oneOf [ types.str types.types.ints.positive ];
       default = "50%";
-      description = ''
+      description = lib.mdDoc ''
         Size of tmpfs in percentage.
         Percentage is defined by systemd.
       '';
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index ed552fecec53a..cfb9c39458ea3 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -82,8 +82,8 @@ in
           "default/useradd".text = "GROUP=100 ...";
         }
       '';
-      description = ''
-        Set of files that have to be linked in <filename>/etc</filename>.
+      description = lib.mdDoc ''
+        Set of files that have to be linked in {file}`/etc`.
       '';
 
       type = with types; attrsOf (submodule (
@@ -93,7 +93,7 @@ in
             enable = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether this /etc file should be generated.  This
                 option allows specific /etc files to be disabled.
               '';
@@ -101,9 +101,9 @@ in
 
             target = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of symlink (relative to
-                <filename>/etc</filename>).  Defaults to the attribute
+                {file}`/etc`).  Defaults to the attribute
                 name.
               '';
             };
@@ -111,20 +111,20 @@ in
             text = mkOption {
               default = null;
               type = types.nullOr types.lines;
-              description = "Text of the file.";
+              description = lib.mdDoc "Text of the file.";
             };
 
             source = mkOption {
               type = types.path;
-              description = "Path of the source file.";
+              description = lib.mdDoc "Path of the source file.";
             };
 
             mode = mkOption {
               type = types.str;
               default = "symlink";
               example = "0600";
-              description = ''
-                If set to something else than <literal>symlink</literal>,
+              description = lib.mdDoc ''
+                If set to something else than `symlink`,
                 the file is copied instead of symlinked, with the given
                 file mode.
               '';
@@ -133,7 +133,7 @@ in
             uid = mkOption {
               default = 0;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 UID of created file. Only takes effect when the file is
                 copied (that is, the mode is not 'symlink').
                 '';
@@ -142,7 +142,7 @@ in
             gid = mkOption {
               default = 0;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 GID of created file. Only takes effect when the file is
                 copied (that is, the mode is not 'symlink').
               '';
@@ -151,20 +151,20 @@ in
             user = mkOption {
               default = "+${toString config.uid}";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 User name of created file.
                 Only takes effect when the file is copied (that is, the mode is not 'symlink').
-                Changing this option takes precedence over <literal>uid</literal>.
+                Changing this option takes precedence over `uid`.
               '';
             };
 
             group = mkOption {
               default = "+${toString config.gid}";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Group name of created file.
                 Only takes effect when the file is copied (that is, the mode is not 'symlink').
-                Changing this option takes precedence over <literal>gid</literal>.
+                Changing this option takes precedence over `gid`.
               '';
             };
 
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 06117d19af468..7837a34b49844 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -16,33 +16,33 @@ let
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
+        description = lib.mdDoc "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
       };
 
       blkDev = mkOption {
         default = null;
         example = "/dev/sda1";
         type = types.nullOr types.str;
-        description = "Location of the backing encrypted device.";
+        description = lib.mdDoc "Location of the backing encrypted device.";
       };
 
       label = mkOption {
         default = null;
         example = "rootfs";
         type = types.nullOr types.str;
-        description = "Label of the unlocked encrypted device. Set <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> to mount the unlocked device.";
+        description = lib.mdDoc "Label of the unlocked encrypted device. Set `fileSystems.<name?>.device` to `/dev/mapper/<label>` to mount the unlocked device.";
       };
 
       keyFile = mkOption {
         default = null;
         example = "/mnt-root/root/.swapkey";
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Path to a keyfile used to unlock the backing encrypted
           device. At the time this keyfile is accessed, the
-          <literal>neededForBoot</literal> filesystems (see
-          <literal>fileSystems.&lt;name?&gt;.neededForBoot</literal>)
-          will have been mounted under <literal>/mnt-root</literal>,
+          `neededForBoot` filesystems (see
+          `fileSystems.<name?>.neededForBoot`)
+          will have been mounted under `/mnt-root`,
           so the keyfile path should usually start with "/mnt-root/".
         '';
       };
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index b8afe231dd2e1..82adc499ad0e7 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -280,7 +280,8 @@ in
     environment.etc.fstab.text =
       let
         fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" "apfs" ];
-        skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
+        isBindMount = fs: builtins.elem "bind" fs.options;
+        skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
         # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
         escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
         swapOptions = sw: concatStringsSep "," (
diff --git a/nixos/modules/tasks/snapraid.nix b/nixos/modules/tasks/snapraid.nix
index c8dde5b48993a..634ffa0311134 100644
--- a/nixos/modules/tasks/snapraid.nix
+++ b/nixos/modules/tasks/snapraid.nix
@@ -14,7 +14,7 @@ in
         d2 = "/mnt/disk2/";
         d3 = "/mnt/disk3/";
       };
-      description = "SnapRAID data disks.";
+      description = lib.mdDoc "SnapRAID data disks.";
       type = attrsOf str;
     };
     parityFiles = mkOption {
@@ -27,7 +27,7 @@ in
         "/mnt/diskt/snapraid.5-parity"
         "/mnt/disku/snapraid.6-parity"
       ];
-      description = "SnapRAID parity files.";
+      description = lib.mdDoc "SnapRAID parity files.";
       type = listOf str;
     };
     contentFiles = mkOption {
@@ -37,46 +37,46 @@ in
         "/mnt/disk1/snapraid.content"
         "/mnt/disk2/snapraid.content"
       ];
-      description = "SnapRAID content list files.";
+      description = lib.mdDoc "SnapRAID content list files.";
       type = listOf str;
     };
     exclude = mkOption {
       default = [ ];
       example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
-      description = "SnapRAID exclude directives.";
+      description = lib.mdDoc "SnapRAID exclude directives.";
       type = listOf str;
     };
     touchBeforeSync = mkOption {
       default = true;
       example = false;
-      description =
-        "Whether <command>snapraid touch</command> should be run before <command>snapraid sync</command>.";
+      description = lib.mdDoc
+        "Whether {command}`snapraid touch` should be run before {command}`snapraid sync`.";
       type = bool;
     };
     sync.interval = mkOption {
       default = "01:00";
       example = "daily";
-      description = "How often to run <command>snapraid sync</command>.";
+      description = lib.mdDoc "How often to run {command}`snapraid sync`.";
       type = str;
     };
     scrub = {
       interval = mkOption {
         default = "Mon *-*-* 02:00:00";
         example = "weekly";
-        description = "How often to run <command>snapraid scrub</command>.";
+        description = lib.mdDoc "How often to run {command}`snapraid scrub`.";
         type = str;
       };
       plan = mkOption {
         default = 8;
         example = 5;
-        description =
-          "Percent of the array that should be checked by <command>snapraid scrub</command>.";
+        description = lib.mdDoc
+          "Percent of the array that should be checked by {command}`snapraid scrub`.";
         type = int;
       };
       olderThan = mkOption {
         default = 10;
         example = 20;
-        description =
+        description = lib.mdDoc
           "Number of days since data was last scrubbed before it can be scrubbed again.";
         type = int;
       };
@@ -90,7 +90,7 @@ in
         autosave 500
         pool /pool
       '';
-      description = "Extra config options for SnapRAID.";
+      description = lib.mdDoc "Extra config options for SnapRAID.";
       type = lines;
     };
   };
diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index 0b53a6d152d09..26d6bd914d5ea 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -10,7 +10,7 @@ in {
     };
 
     mdadmConf = lib.mkOption {
-      description = "Contents of <filename>/etc/mdadm.conf</filename> in initrd.";
+      description = lib.mdDoc "Contents of {file}`/etc/mdadm.conf` in initrd.";
       type = lib.types.lines;
       default = "";
     };
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index c70da573533fa..6ba71ede7df0c 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -10,7 +10,7 @@ let
     address = mkOption {
       default = addr;
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         IPv${toString v} ${name} address.
       '';
     };
@@ -18,9 +18,9 @@ let
     prefixLength = mkOption {
       default = pref;
       type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
-      description = ''
+      description = lib.mdDoc ''
         Subnet mask of the ${name} address, specified as the number of
-        bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+        bits in the prefix (`${if v == 4 then "24" else "64"}`).
       '';
     };
   };
@@ -37,7 +37,7 @@ in
       default = pkgs.anbox.image;
       defaultText = literalExpression "pkgs.anbox.image";
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         Base android image for Anbox.
       '';
     };
@@ -45,7 +45,7 @@ in
     extraInit = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra shell commands to be run inside the container image during init.
       '';
     };
@@ -57,7 +57,7 @@ in
       dns = mkOption {
         default = "1.1.1.1";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Container DNS server.
         '';
       };
diff --git a/nixos/modules/virtualisation/build-vm.nix b/nixos/modules/virtualisation/build-vm.nix
index 4a4694950f988..e94254416316b 100644
--- a/nixos/modules/virtualisation/build-vm.nix
+++ b/nixos/modules/virtualisation/build-vm.nix
@@ -25,8 +25,8 @@ in
   options = {
 
     virtualisation.vmVariant = mkOption {
-      description = ''
-        Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm</literal>.
+      description = lib.mdDoc ''
+        Machine configuration to be added for the vm script produced by `nixos-rebuild build-vm`.
       '';
       inherit (vmVariant) type;
       default = {};
@@ -34,8 +34,8 @@ in
     };
 
     virtualisation.vmVariantWithBootLoader = mkOption {
-      description = ''
-        Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm-with-bootloader</literal>.
+      description = lib.mdDoc ''
+        Machine configuration to be added for the vm script produced by `nixos-rebuild build-vm-with-bootloader`.
       '';
       inherit (vmVariantWithBootLoader) type;
       default = {};
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index cea3d51d3aefe..a9a2f3c1488ae 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -31,7 +31,7 @@ in
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option enables the common /etc/containers configuration module.
         '';
       };
@@ -39,13 +39,13 @@ in
     ociSeccompBpfHook.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the OCI seccomp BPF hook";
+      description = lib.mdDoc "Enable the OCI seccomp BPF hook";
     };
 
     containersConf.settings = mkOption {
       type = toml.type;
       default = { };
-      description = "containers.conf configuration";
+      description = lib.mdDoc "containers.conf configuration";
     };
 
     containersConf.cniPlugins = mkOption {
@@ -60,7 +60,7 @@ in
           pkgs.cniPlugins.dnsname
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         CNI plugins to install on the system.
       '';
     };
@@ -74,14 +74,14 @@ in
           runroot = "/run/containers/storage";
         };
       };
-      description = "storage.conf configuration";
+      description = lib.mdDoc "storage.conf configuration";
     };
 
     registries = {
       search = mkOption {
         type = types.listOf types.str;
         default = [ "docker.io" "quay.io" ];
-        description = ''
+        description = lib.mdDoc ''
           List of repositories to search.
         '';
       };
@@ -89,7 +89,7 @@ in
       insecure = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of insecure repositories.
         '';
       };
@@ -97,7 +97,7 @@ in
       block = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of blocked repositories.
         '';
       };
@@ -116,10 +116,10 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Signature verification policy file.
         If this option is empty the default policy file from
-        <literal>skopeo</literal> will be used.
+        `skopeo` will be used.
       '';
     };
 
diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix
index 0ff2ee591f24a..a57c89245f2e1 100644
--- a/nixos/modules/virtualisation/digital-ocean-image.nix
+++ b/nixos/modules/virtualisation/digital-ocean-image.nix
@@ -13,7 +13,7 @@ in
       type = with types; either (enum [ "auto" ]) int;
       default = "auto";
       example = 4096;
-      description = ''
+      description = lib.mdDoc ''
         Size of disk image. Unit is MB.
       '';
     };
@@ -21,12 +21,12 @@ in
     virtualisation.digitalOceanImage.configFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A path to a configuration file which will be placed at
-        <literal>/etc/nixos/configuration.nix</literal> and be used when switching
-        to a new configuration. If set to <literal>null</literal>, a default
+        `/etc/nixos/configuration.nix` and be used when switching
+        to a new configuration. If set to `null`, a default
         configuration is used that imports
-        <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+        `(modulesPath + "/virtualisation/digital-ocean-config.nix")`.
       '';
     };
 
@@ -34,7 +34,7 @@ in
       type = types.enum [ "gzip" "bzip2" ];
       default = "gzip";
       example = "bzip2";
-      description = ''
+      description = lib.mdDoc ''
         Disk image compression method. Choose bzip2 to generate smaller images that
         take longer to generate but will consume less metered storage space on your
         Digital Ocean account.
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
index df30104b7d7fd..e29e34c4775fc 100644
--- a/nixos/modules/virtualisation/digital-ocean-init.nix
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -15,18 +15,18 @@ in {
     type = types.bool;
     default = true;
     example = true;
-    description = "Whether to reconfigure the system from Digital Ocean user data";
+    description = lib.mdDoc "Whether to reconfigure the system from Digital Ocean user data";
   };
   options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
     type = types.path;
     default = defaultConfigFile;
     defaultText = literalDocBook ''
       The default configuration imports user-data if applicable and
-      <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+      `(modulesPath + "/virtualisation/digital-ocean-config.nix")`.
     '';
-    description = ''
+    description = lib.mdDoc ''
       A path to a configuration file which will be placed at
-      <literal>/etc/nixos/configuration.nix</literal> and be used when switching to
+      `/etc/nixos/configuration.nix` and be used when switching to
       a new configuration.
     '';
   };
diff --git a/nixos/modules/virtualisation/docker-rootless.nix b/nixos/modules/virtualisation/docker-rootless.nix
index b814fa1c4358c..f4e4bdc0963a7 100644
--- a/nixos/modules/virtualisation/docker-rootless.nix
+++ b/nixos/modules/virtualisation/docker-rootless.nix
@@ -18,18 +18,18 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         This option enables docker in a rootless mode, a daemon that manages
         linux containers. To interact with the daemon, one needs to set
-        <command>DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock</command>.
+        {command}`DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock`.
       '';
     };
 
     setSocketVariable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Point <command>DOCKER_HOST</command> to rootless Docker instance for
+      description = lib.mdDoc ''
+        Point {command}`DOCKER_HOST` to rootless Docker instance for
         normal users by default.
       '';
     };
@@ -41,7 +41,7 @@ in
         ipv6 = true;
         "fixed-cidr-v6" = "fd00::/80";
       };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
         See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
       '';
@@ -51,7 +51,7 @@ in
       default = pkgs.docker;
       defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         Docker package to be used in the module.
       '';
     };
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index fa5fe99730440..81cdf1dd72b46 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -14,18 +14,18 @@ let
 
         image = mkOption {
           type = with types; str;
-          description = "OCI image to run.";
+          description = lib.mdDoc "OCI image to run.";
           example = "library/hello-world";
         };
 
         imageFile = mkOption {
           type = with types; nullOr package;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to an image file to load before running the image. This can
             be used to bypass pulling the image from the registry.
 
-            The <literal>image</literal> attribute must match the name and
+            The `image` attribute must match the name and
             tag of the image contained in this file, as they will be used to
             run the container with that image. If they do not match, the
             image will be pulled from the registry as usual.
@@ -38,20 +38,20 @@ let
           username = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Username for login.";
+            description = lib.mdDoc "Username for login.";
           };
 
           passwordFile = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Path to file containing password.";
+            description = lib.mdDoc "Path to file containing password.";
             example = "/etc/nixos/dockerhub-password.txt";
           };
 
           registry = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Registry where to login to.";
+            description = lib.mdDoc "Registry where to login to.";
             example = "https://docker.pkg.github.com";
           };
 
@@ -60,7 +60,7 @@ let
         cmd = mkOption {
           type =  with types; listOf str;
           default = [];
-          description = "Commandline arguments to pass to the image's entrypoint.";
+          description = lib.mdDoc "Commandline arguments to pass to the image's entrypoint.";
           example = literalExpression ''
             ["--port=9000"]
           '';
@@ -68,7 +68,7 @@ let
 
         entrypoint = mkOption {
           type = with types; nullOr str;
-          description = "Override the default entrypoint of the image.";
+          description = lib.mdDoc "Override the default entrypoint of the image.";
           default = null;
           example = "/bin/my-app";
         };
@@ -76,7 +76,7 @@ let
         environment = mkOption {
           type = with types; attrsOf str;
           default = {};
-          description = "Environment variables to set for this container.";
+          description = lib.mdDoc "Environment variables to set for this container.";
           example = literalExpression ''
             {
               DATABASE_HOST = "db.example.com";
@@ -88,7 +88,7 @@ let
         environmentFiles = mkOption {
           type = with types; listOf path;
           default = [];
-          description = "Environment files for this container.";
+          description = lib.mdDoc "Environment files for this container.";
           example = literalExpression ''
             [
               /path/to/.env
@@ -100,15 +100,15 @@ let
         log-driver = mkOption {
           type = types.str;
           default = "journald";
-          description = ''
+          description = lib.mdDoc ''
             Logging driver for the container.  The default of
-            <literal>"journald"</literal> means that the container's logs will be
+            `"journald"` means that the container's logs will be
             handled as part of the systemd unit.
 
             For more details and a full list of logging drivers, refer to respective backends documentation.
 
             For Docker:
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">Docker engine documentation</link>
+            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver)
 
             For Podman:
             Refer to the docker-run(1) man page.
@@ -118,49 +118,27 @@ let
         ports = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             Network ports to publish from the container to the outer host.
 
             Valid formats:
+            - `<ip>:<hostPort>:<containerPort>`
+            - `<ip>::<containerPort>`
+            - `<hostPort>:<containerPort>`
+            - `<containerPort>`
 
-            <itemizedlist>
-              <listitem>
-                <para>
-                  <literal>&lt;ip&gt;:&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;ip&gt;::&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-            </itemizedlist>
-
-            Both <literal>hostPort</literal> and
-            <literal>containerPort</literal> can be specified as a range of
+            Both `hostPort` and `containerPort` can be specified as a range of
             ports.  When specifying ranges for both, the number of container
             ports in the range must match the number of host ports in the
-            range.  Example: <literal>1234-1236:1234-1236/tcp</literal>
+            range.  Example: `1234-1236:1234-1236/tcp`
 
-            When specifying a range for <literal>hostPort</literal> only, the
-            <literal>containerPort</literal> must <emphasis>not</emphasis> be a
-            range.  In this case, the container port is published somewhere
-            within the specified <literal>hostPort</literal> range.  Example:
-            <literal>1234-1236:1234/tcp</literal>
+            When specifying a range for `hostPort` only, the `containerPort`
+            must *not* be a range.  In this case, the container port is published
+            somewhere within the specified `hostPort` range.
+            Example: `1234-1236:1234/tcp`
 
             Refer to the
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
-            Docker engine documentation</link> for full details.
+            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
           '';
           example = literalExpression ''
             [
@@ -172,7 +150,7 @@ let
         user = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Override the username or UID (and optionally groupname or GID) used
             in the container.
           '';
@@ -182,16 +160,15 @@ let
         volumes = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             List of volumes to attach to this container.
 
-            Note that this is a list of <literal>"src:dst"</literal> strings to
-            allow for <literal>src</literal> to refer to
-            <literal>/nix/store</literal> paths, which would be difficult with an
-            attribute set.  There are also a variety of mount options available
-            as a third field; please refer to the
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
-            docker engine documentation</link> for details.
+            Note that this is a list of `"src:dst"` strings to
+            allow for `src` to refer to `/nix/store` paths, which
+            would be difficult with an attribute set.  There are
+            also a variety of mount options available as a third
+            field; please refer to the
+            [docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
           '';
           example = literalExpression ''
             [
@@ -204,17 +181,17 @@ let
         workdir = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Override the default working directory for the container.";
+          description = lib.mdDoc "Override the default working directory for the container.";
           example = "/var/lib/hello_world";
         };
 
         dependsOn = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             Define which other containers this one depends on. They will be added to both After and Requires for the unit.
 
-            Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
+            Use the same name as the attribute under `virtualisation.oci-containers.containers`.
           '';
           example = literalExpression ''
             virtualisation.oci-containers.containers = {
@@ -229,7 +206,7 @@ let
         extraOptions = mkOption {
           type = with types; listOf str;
           default = [];
-          description = "Extra options for <command>${defaultBackend} run</command>.";
+          description = lib.mdDoc "Extra options for {command}`${defaultBackend} run`.";
           example = literalExpression ''
             ["--network=host"]
           '';
@@ -238,7 +215,7 @@ let
         autoStart = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             When enabled, the container is automatically started on boot.
             If this option is set to false, the container has to be started on-demand via its service.
           '';
@@ -339,13 +316,13 @@ in {
     backend = mkOption {
       type = types.enum [ "podman" "docker" ];
       default = if versionAtLeast config.system.stateVersion "22.05" then "podman" else "docker";
-      description = "The underlying Docker implementation to use.";
+      description = lib.mdDoc "The underlying Docker implementation to use.";
     };
 
     containers = mkOption {
       default = {};
       type = types.attrsOf (types.submodule containerOptions);
-      description = "OCI (Docker) containers to run as systemd services.";
+      description = lib.mdDoc "OCI (Docker) containers to run as systemd services.";
     };
 
   };
diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index 436a375fb5ebc..32646f60f8e04 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -13,7 +13,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Open vSwitch. A configuration daemon (ovs-server)
         will be started.
         '';
@@ -22,9 +22,9 @@ in {
     resetOnStart = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to reset the Open vSwitch configuration database to a default
-        configuration on every start of the systemd <literal>ovsdb.service</literal>.
+        configuration on every start of the systemd `ovsdb.service`.
         '';
     };
 
@@ -32,7 +32,7 @@ in {
       type = types.package;
       default = pkgs.openvswitch;
       defaultText = literalExpression "pkgs.openvswitch";
-      description = ''
+      description = lib.mdDoc ''
         Open vSwitch package to use.
       '';
     };
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index e87f540fd57cb..5b2d81eeb68fc 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -870,7 +870,7 @@ in
       (mkIf pkgs.stdenv.hostPlatform.isx86 [
         "-usb" "-device usb-tablet,bus=usb-bus.0"
       ])
-      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+      (mkIf pkgs.stdenv.hostPlatform.isAarch [
         "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
       ])
       (let
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 1a0c4df42cb3d..0c095c01ad80f 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -14,42 +14,42 @@ in {
         type = with types; either (enum [ "auto" ]) int;
         default = "auto";
         example = 50 * 1024;
-        description = ''
+        description = lib.mdDoc ''
           The size of the VirtualBox base image in MiB.
         '';
       };
       baseImageFreeSpace = mkOption {
         type = with types; int;
         default = 30 * 1024;
-        description = ''
+        description = lib.mdDoc ''
           Free space in the VirtualBox base image in MiB.
         '';
       };
       memorySize = mkOption {
         type = types.int;
         default = 1536;
-        description = ''
+        description = lib.mdDoc ''
           The amount of RAM the VirtualBox appliance can use in MiB.
         '';
       };
       vmDerivationName = mkOption {
         type = types.str;
         default = "nixos-ova-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
-        description = ''
+        description = lib.mdDoc ''
           The name of the derivation for the VirtualBox appliance.
         '';
       };
       vmName = mkOption {
         type = types.str;
         default = "NixOS ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
-        description = ''
+        description = lib.mdDoc ''
           The name of the VirtualBox appliance.
         '';
       };
       vmFileName = mkOption {
         type = types.str;
         default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.ova";
-        description = ''
+        description = lib.mdDoc ''
           The file name of the VirtualBox appliance.
         '';
       };
@@ -60,10 +60,10 @@ in {
           rtcuseutc = "on";
           usb = "off";
         };
-        description = ''
+        description = lib.mdDoc ''
           Parameters passed to the Virtualbox appliance.
 
-          Run <literal>VBoxManage modifyvm --help</literal> to see more options.
+          Run `VBoxManage modifyvm --help` to see more options.
         '';
       };
       exportParams = mkOption {
@@ -72,14 +72,14 @@ in {
           "--vsys" "0" "--vendor" "ACME Inc."
         ];
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Parameters passed to the Virtualbox export command.
 
-          Run <literal>VBoxManage export --help</literal> to see more options.
+          Run `VBoxManage export --help` to see more options.
         '';
       };
       extraDisk = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Optional extra disk/hdd configuration.
           The disk will be an 'ext4' partition on a separate VMDK file.
         '';
@@ -93,16 +93,16 @@ in {
           options = {
             size = mkOption {
               type = types.int;
-              description = "Size in MiB";
+              description = lib.mdDoc "Size in MiB";
             };
             label = mkOption {
               type = types.str;
               default = "vm-extra-storage";
-              description = "Label for the disk partition";
+              description = lib.mdDoc "Label for the disk partition";
             };
             mountPoint = mkOption {
               type = types.str;
-              description = "Path where to mount this disk.";
+              description = lib.mdDoc "Path where to mount this disk.";
             };
           };
         });
diff --git a/nixos/modules/virtualisation/waydroid.nix b/nixos/modules/virtualisation/waydroid.nix
index 2c0b658948dd5..84abf6065810e 100644
--- a/nixos/modules/virtualisation/waydroid.nix
+++ b/nixos/modules/virtualisation/waydroid.nix
@@ -34,7 +34,7 @@ in
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isEnabled "ANDROID_BINDER_IPC")
       (isEnabled "ANDROID_BINDERFS")
-      (isEnabled "ASHMEM")
+      (isEnabled "ASHMEM") # FIXME Needs memfd support instead on Linux 5.18 and waydroid 1.2.1
     ];
 
     /* NOTE: we always enable this flag even if CONFIG_PSI_DEFAULT_DISABLED is not on
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index a999efcb44e64..25d06e3c721da 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -37,7 +37,7 @@ in
       type = types.package;
       defaultText = literalExpression "pkgs.xen";
       example = literalExpression "pkgs.xen-light";
-      description = ''
+      description = lib.mdDoc ''
         The package used for Xen binary.
       '';
       relatedPackages = [ "xen" "xen-light" ];
@@ -47,7 +47,7 @@ in
       type = types.package;
       defaultText = literalExpression "pkgs.xen";
       example = literalExpression "pkgs.qemu_xen-light";
-      description = ''
+      description = lib.mdDoc ''
         The package with qemu binaries for dom0 qemu and xendomains.
       '';
       relatedPackages = [ "xen"
@@ -59,7 +59,7 @@ in
       mkOption {
         default = [];
         type = types.listOf types.str;
-        description =
+        description = lib.mdDoc
           ''
             Parameters passed to the Xen hypervisor at boot time.
           '';
@@ -70,7 +70,7 @@ in
         default = 0;
         example = 512;
         type = types.addCheck types.int (n: n >= 0);
-        description =
+        description = lib.mdDoc
           ''
             Amount of memory (in MiB) allocated to Domain 0 on boot.
             If set to 0, all memory is assigned to Domain 0.
@@ -81,7 +81,7 @@ in
         name = mkOption {
           default = "xenbr0";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
               Name of bridge the Xen domUs connect to.
             '';
         };
@@ -89,7 +89,7 @@ in
         address = mkOption {
           type = types.str;
           default = "172.16.0.1";
-          description = ''
+          description = lib.mdDoc ''
             IPv4 address of the bridge.
           '';
         };
@@ -97,9 +97,9 @@ in
         prefixLength = mkOption {
           type = types.addCheck types.int (n: n >= 0 && n <= 32);
           default = 16;
-          description = ''
+          description = lib.mdDoc ''
             Subnet mask of the bridge interface, specified as the number of
-            bits in the prefix (<literal>24</literal>).
+            bits in the prefix (`24`).
             A DHCP server will provide IP addresses for the whole, remaining
             subnet.
           '';
@@ -108,8 +108,8 @@ in
         forwardDns = mkOption {
           type = types.bool;
           default = false;
-          description = ''
-            If set to <literal>true</literal>, the DNS queries from the
+          description = lib.mdDoc ''
+            If set to `true`, the DNS queries from the
             hosts connected to the bridge will be forwarded to the DNS
             servers specified in /etc/resolv.conf .
             '';
@@ -120,7 +120,7 @@ in
     virtualisation.xen.stored =
       mkOption {
         type = types.path;
-        description =
+        description = lib.mdDoc
           ''
             Xen Store daemon to use. Defaults to oxenstored of the xen package.
           '';
@@ -130,7 +130,7 @@ in
         extraConfig = mkOption {
           type = types.lines;
           default = "";
-          description =
+          description = lib.mdDoc
             ''
               Options defined here will override the defaults for xendomains.
               The default options can be seen in the file included from
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 03a7f17c07cc7..ff54a327424e8 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -254,7 +254,7 @@ in {
   jibri = handleTest ./jibri.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
-  k3s-single-node = handleTest ./k3s-single-node.nix {};
+  k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
   kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
@@ -282,6 +282,7 @@ in {
   libuiohook = handleTest ./libuiohook.nix {};
   lidarr = handleTest ./lidarr.nix {};
   lightdm = handleTest ./lightdm.nix {};
+  lighttpd = handleTest ./lighttpd.nix {};
   limesurvey = handleTest ./limesurvey.nix {};
   litestream = handleTest ./litestream.nix {};
   locate = handleTest ./locate.nix {};
@@ -522,6 +523,7 @@ in {
   starship = handleTest ./starship.nix {};
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
+  stunnel = handleTest ./stunnel.nix {};
   sudo = handleTest ./sudo.nix {};
   swap-partition = handleTest ./swap-partition.nix {};
   sway = handleTest ./sway.nix {};
diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix
index 63b5860f0d206..3f111426db388 100644
--- a/nixos/tests/jenkins.nix
+++ b/nixos/tests/jenkins.nix
@@ -81,7 +81,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     in ''
     start_all()
 
-    master.wait_for_unit("jenkins")
+    master.wait_for_unit("default.target")
 
     assert "Authentication required" in master.succeed("curl http://localhost:8080")
 
@@ -96,8 +96,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     with subtest("jobs are declarative"):
         # Check that jobs are created on disk.
-        master.wait_for_unit("jenkins-job-builder")
-        master.wait_until_fails("systemctl is-active jenkins-job-builder")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/job-1/config.xml")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
@@ -115,8 +113,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         )
 
         # Check that jobs are removed from disk.
-        master.wait_for_unit("jenkins-job-builder")
-        master.wait_until_fails("systemctl is-active jenkins-job-builder")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/job-1/config.xml")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix
new file mode 100644
index 0000000000000..07d93c41c7a68
--- /dev/null
+++ b/nixos/tests/k3s/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+{
+  # Run a single node k3s cluster and verify a pod can run
+  single-node = import ./single-node.nix { inherit system pkgs; };
+  # Run a multi-node k3s cluster and verify pod networking works across nodes
+  multi-node = import ./multi-node.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix
new file mode 100644
index 0000000000000..afb8c78f2339e
--- /dev/null
+++ b/nixos/tests/k3s/multi-node.nix
@@ -0,0 +1,137 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  let
+    imageEnv = pkgs.buildEnv {
+      name = "k3s-pause-image-env";
+      paths = with pkgs; [ tini bashInteractive coreutils socat ];
+    };
+    pauseImage = pkgs.dockerTools.streamLayeredImage {
+      name = "test.local/pause";
+      tag = "local";
+      contents = imageEnv;
+      config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
+    };
+    # A daemonset that responds 'server' on port 8000
+    networkTestDaemonset = pkgs.writeText "test.yml" ''
+      apiVersion: apps/v1
+      kind: DaemonSet
+      metadata:
+        name: test
+        labels:
+          name: test
+      spec:
+        selector:
+          matchLabels:
+            name: test
+        template:
+          metadata:
+            labels:
+              name: test
+          spec:
+            containers:
+            - name: test
+              image: test.local/pause:local
+              imagePullPolicy: Never
+              resources:
+                limits:
+                  memory: 20Mi
+              command: ["socat", "TCP4-LISTEN:8000,fork", "EXEC:echo server"]
+    '';
+    tokenFile = pkgs.writeText "token" "p@s$w0rd";
+  in
+  {
+    name = "k3s-multi-node";
+
+    nodes = {
+      server = { pkgs, ... }: {
+        environment.systemPackages = with pkgs; [ gzip jq ];
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "server";
+          package = pkgs.k3s;
+          extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local --node-ip 192.168.1.1";
+        };
+        networking.firewall.allowedTCPPorts = [ 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.1";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+
+      agent = { pkgs, ... }: {
+        virtualisation.memorySize = 1024;
+        virtualisation.diskSize = 2048;
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "agent";
+          serverAddr = "https://192.168.1.1:6443";
+          extraFlags = "--pause-image test.local/pause:local --node-ip 192.168.1.2";
+        };
+        networking.firewall.allowedTCPPorts = [ 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.2";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ euank ];
+    };
+
+    testScript = ''
+      start_all()
+      machines = [server, agent]
+      for m in machines:
+          m.wait_for_unit("k3s")
+
+      # wait for the agent to show up
+      server.wait_until_succeeds("k3s kubectl get node agent")
+
+      for m in machines:
+          m.succeed("k3s check-config")
+          m.succeed(
+              "${pauseImage} | k3s ctr image import -"
+          )
+
+      server.succeed("k3s kubectl cluster-info")
+      # Also wait for our service account to show up; it takes a sec
+      server.wait_until_succeeds("k3s kubectl get serviceaccount default")
+
+      # Now create a pod on each node via a daemonset and verify they can talk to each other.
+      server.succeed("k3s kubectl apply -f ${networkTestDaemonset}")
+      server.wait_until_succeeds(f'[ "$(k3s kubectl get ds test -o json | jq .status.numberReady)" -eq {len(machines)} ]')
+
+      # Get pod IPs
+      pods = server.succeed("k3s kubectl get po -o json | jq '.items[].metadata.name' -r").splitlines()
+      pod_ips = [server.succeed(f"k3s kubectl get po {name} -o json | jq '.status.podIP' -cr").strip() for name in pods]
+
+      # Verify each server can ping each pod ip
+      for pod_ip in pod_ips:
+          server.succeed(f"ping -c 1 {pod_ip}")
+          agent.succeed(f"ping -c 1 {pod_ip}")
+
+      # Verify the pods can talk to each other
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[0]} -- socat TCP:{pod_ips[1]}:8000 -")
+      assert resp.strip() == "server"
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[1]} -- socat TCP:{pod_ips[0]}:8000 -")
+      assert resp.strip() == "server"
+
+      # Cleanup
+      server.succeed("k3s kubectl delete -f ${networkTestDaemonset}")
+
+      for m in machines:
+          m.shutdown()
+    '';
+  })
diff --git a/nixos/tests/k3s-single-node.nix b/nixos/tests/k3s/single-node.nix
index fb6510ee087bc..27e1e455e6415 100644
--- a/nixos/tests/k3s-single-node.nix
+++ b/nixos/tests/k3s/single-node.nix
@@ -1,5 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
+import ../make-test-python.nix ({ pkgs, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -11,20 +10,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
       contents = imageEnv;
       config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
     };
-    # Don't use the default service account because there's a race where it may
-    # not be created yet; make our own instead.
     testPodYaml = pkgs.writeText "test.yml" ''
       apiVersion: v1
-      kind: ServiceAccount
-      metadata:
-        name: test
-      ---
-      apiVersion: v1
       kind: Pod
       metadata:
         name: test
       spec:
-        serviceAccountName: test
         containers:
         - name: test
           image: test.local/pause:local
@@ -66,13 +57,14 @@ import ./make-test-python.nix ({ pkgs, ... }:
       machine.wait_for_unit("k3s")
       machine.succeed("k3s kubectl cluster-info")
       machine.fail("sudo -u noprivs k3s kubectl cluster-info")
-      # FIXME: this fails with the current nixos kernel config; once it passes, we should uncomment it
-      # machine.succeed("k3s check-config")
+      machine.succeed("k3s check-config")
 
       machine.succeed(
           "${pauseImage} | k3s ctr image import -"
       )
 
+      # Also wait for our service account to show up; it takes a sec
+      machine.wait_until_succeeds("k3s kubectl get serviceaccount default")
       machine.succeed("k3s kubectl apply -f ${testPodYaml}")
       machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test")
       machine.succeed("k3s kubectl delete -f ${testPodYaml}")
diff --git a/nixos/tests/kea.nix b/nixos/tests/kea.nix
index 6b345893108fe..b1d5894cc7cd2 100644
--- a/nixos/tests/kea.nix
+++ b/nixos/tests/kea.nix
@@ -1,6 +1,8 @@
 import ./make-test-python.nix ({ pkgs, lib, ...}: {
   meta.maintainers = with lib.maintainers; [ hexa ];
 
+  name = "kea";
+
   nodes = {
     router = { config, pkgs, ... }: {
       virtualisation.vlans = [ 1 ];
diff --git a/nixos/tests/lighttpd.nix b/nixos/tests/lighttpd.nix
new file mode 100644
index 0000000000000..36e2745c55c15
--- /dev/null
+++ b/nixos/tests/lighttpd.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "lighttpd";
+  meta.maintainers = with lib.maintainers; [ bjornfor ];
+
+  nodes = {
+    server = {
+      services.lighttpd.enable = true;
+      services.lighttpd.document-root = pkgs.runCommand "document-root" {} ''
+        mkdir -p "$out"
+        echo "hello nixos test" > "$out/file.txt"
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("lighttpd.service")
+    res = server.succeed("curl --fail http://localhost/file.txt")
+    assert "hello nixos test" in res, f"bad server response: '{res}'"
+  '';
+})
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index 45165b04bf899..9e378fe6a52d3 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -16,6 +16,10 @@ foldl
       inherit system pkgs;
       nextcloudVersion = ver;
     };
+    "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
   })
 { }
   [ 23 24 ]
diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
new file mode 100644
index 0000000000000..fda05bacb4fe4
--- /dev/null
+++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -0,0 +1,118 @@
+import ../make-test-python.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-declarative-redis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "/run/postgresql";
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+        secretFile = "/etc/nextcloud-secrets.json";
+
+        extraOptions.redis = {
+          host = "/run/redis/redis.sock";
+          port = 0;
+          dbindex = 0;
+          timeout = 1.5;
+          # password handled via secretfile below
+        };
+        extraOptions.memcache = {
+          local = "\OC\Memcache\Redis";
+          locking = "\OC\Memcache\Redis";
+        };
+      };
+
+      services.redis = {
+        enable = true;
+      };
+
+      systemd.services.nextcloud-setup= {
+        requires = ["postgresql.service"];
+        after = [
+          "postgresql.service"
+        ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "nextcloud" ];
+        ensureUsers = [
+          { name = "nextcloud";
+            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+
+      # This file is meant to contain secret options which should
+      # not go into the nix store. Here it is just used to set the
+      # databyse type to postgres.
+      environment.etc."nextcloud-secrets.json".text = ''
+        {
+          "redis": {
+            "password": "secret"
+          }
+        }
+      '';
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    start_all()
+    nextcloud.wait_for_unit("multi-user.target")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+
+    # redis cache should not be empty
+    nextcloud.fail("redis-cli KEYS * | grep -q 'empty array'")
+  '';
+})
diff --git a/nixos/tests/nginx-etag.nix b/nixos/tests/nginx-etag.nix
index b69511d081d4b..6f45eacf8b41a 100644
--- a/nixos/tests/nginx-etag.nix
+++ b/nixos/tests/nginx-etag.nix
@@ -53,14 +53,14 @@ import ./make-test-python.nix {
 
           driver.implicitly_wait(20)
           driver.get('http://server/')
-          driver.find_element_by_xpath('//div[@foo="bar"]')
+          driver.find_element('xpath', '//div[@foo="bar"]')
           open('/tmp/passed_stage1', 'w')
 
           while not os.path.exists('/tmp/proceed'):
               time.sleep(0.5)
 
           driver.get('http://server/')
-          driver.find_element_by_xpath('//div[@foo="yay"]')
+          driver.find_element('xpath', '//div[@foo="yay"]')
           open('/tmp/passed', 'w')
         '';
       in [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
diff --git a/nixos/tests/podgrab.nix b/nixos/tests/podgrab.nix
index e5a340dc2ac83..dc9dfebaf49bd 100644
--- a/nixos/tests/podgrab.nix
+++ b/nixos/tests/podgrab.nix
@@ -22,11 +22,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     start_all()
 
     default.wait_for_unit("podgrab")
-    default.wait_for_open_port(defaultPort)
+    default.wait_for_open_port(${toString defaultPort})
     default.succeed("curl --fail http://localhost:${toString defaultPort}")
 
     customized.wait_for_unit("podgrab")
-    customized.wait_for_open_port(customPort)
+    customized.wait_for_open_port(${toString customPort})
     customized.succeed("curl --fail http://localhost:${toString customPort}")
   '';
 
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 7523d5e5ed5da..75fffe9d9a84d 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -63,6 +63,12 @@ import ./make-test-python.nix (
               inherit repository passwordFile;
               pruneOpts = [ "--keep-last 1" ];
             };
+            custompackage = {
+              inherit repository passwordFile paths;
+              package = pkgs.writeShellScriptBin "restic" ''
+                echo "$@" >> /tmp/fake-restic.log;
+              '';
+            };
           };
 
           environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
@@ -76,6 +82,7 @@ import ./make-test-python.nix (
           "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
           '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots"',
           "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
+          "grep 'backup .* /opt' /tmp/fake-restic.log",
       )
       server.succeed(
           "mkdir -p /opt",
@@ -89,6 +96,8 @@ import ./make-test-python.nix (
           '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
           '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
           '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          "systemctl start restic-backups-custompackage.service",
+          "grep 'backup .* /opt' /tmp/fake-restic.log",
           "timedatectl set-time '2017-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
           "rm /opt/backupCleanupCommand",
diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix
index fbe9cdf84d05a..5e2b648c7cf59 100644
--- a/nixos/tests/signal-desktop.nix
+++ b/nixos/tests/signal-desktop.nix
@@ -60,7 +60,7 @@ in {
     )
     # Only SQLCipher should be able to read the encrypted DB:
     machine.fail(
-        "su - alice -c 'sqlite3 ~/.config/Signal/sql/db.sqlite .databases'"
+        "su - alice -c 'sqlite3 ~/.config/Signal/sql/db.sqlite .tables'"
     )
     print(machine.succeed(
         "su - alice -c 'sqlcipher ~/.config/Signal/sql/db.sqlite'"
diff --git a/nixos/tests/stunnel.nix b/nixos/tests/stunnel.nix
new file mode 100644
index 0000000000000..22c087290fc7b
--- /dev/null
+++ b/nixos/tests/stunnel.nix
@@ -0,0 +1,174 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  stunnelCommon = {
+    services.stunnel = {
+      enable = true;
+      user = "stunnel";
+    };
+    users.groups.stunnel = { };
+    users.users.stunnel = {
+      isSystemUser = true;
+      group = "stunnel";
+    };
+  };
+  makeCert = { config, pkgs, ... }: {
+    system.activationScripts.create-test-cert = stringAfter [ "users" ] ''
+      ${pkgs.openssl}/bin/openssl req -batch -x509 -newkey rsa -nodes -out /test-cert.pem -keyout /test-key.pem -subj /CN=${config.networking.hostName}
+      ( umask 077; cat /test-key.pem /test-cert.pem > /test-key-and-cert.pem )
+      chown stunnel /test-key.pem /test-key-and-cert.pem
+    '';
+  };
+  serverCommon = { pkgs, ... }: {
+    networking.firewall.allowedTCPPorts = [ 443 ];
+    services.stunnel.servers.https = {
+      accept = "443";
+      connect = 80;
+      cert = "/test-key-and-cert.pem";
+    };
+    systemd.services.simple-webserver = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        cd /etc/webroot
+        ${pkgs.python3}/bin/python -m http.server 80
+      '';
+    };
+  };
+  copyCert = src: dest: filename: ''
+    from shlex import quote
+    ${src}.wait_for_file("/test-key-and-cert.pem")
+    server_cert = ${src}.succeed("cat /test-cert.pem")
+    ${dest}.succeed("echo %s > ${filename}" % quote(server_cert))
+  '';
+
+in {
+  basicServer = makeTest {
+    name = "basicServer";
+
+    nodes = {
+      client = { };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "well met";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      client.succeed("curl --fail --cacert /authorized-server-cert.crt https://server/ > out")
+      client.succeed('[[ "$(< out)" == "well met" ]]')
+    '';
+  };
+
+  serverAndClient = makeTest {
+    name = "serverAndClient";
+
+    nodes = {
+      client = {
+        imports = [ stunnelCommon ];
+        services.stunnel.clients = {
+          httpsClient = {
+            accept = "80";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+          };
+          httpsClientWithHostVerify = {
+            accept = "81";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "server";
+          };
+          httpsClientWithHostVerifyFail = {
+            accept = "82";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "wronghostname";
+          };
+        };
+      };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "hello there";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      # In case stunnel came up before we got the server's cert copied over
+      client.succeed("systemctl reload-or-restart stunnel")
+
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.succeed("curl --fail http://localhost:81/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.fail("curl --fail http://localhost:82/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+
+  mutualAuth = makeTest {
+    name = "mutualAuth";
+
+    nodes = rec {
+      client = {
+        imports = [ makeCert stunnelCommon ];
+        services.stunnel.clients.authenticated-https = {
+          accept = "80";
+          connect = "server:443";
+          verifyPeer = true;
+          CAFile = "/authorized-server-cert.crt";
+          cert = "/test-cert.pem";
+          key = "/test-key.pem";
+        };
+      };
+      wrongclient = client;
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        services.stunnel.servers.https = {
+          CAFile = "/authorized-client-certs.crt";
+          verifyPeer = true;
+        };
+        environment.etc."webroot/index.html".text = "secret handshake";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+      ${copyCert "client" "server" "/authorized-client-certs.crt"}
+      ${copyCert "server" "wrongclient" "/authorized-server-cert.crt"}
+
+      # In case stunnel came up before we got the cross-certs in place
+      client.succeed("systemctl reload-or-restart stunnel")
+      server.succeed("systemctl reload-or-restart stunnel")
+      wrongclient.succeed("systemctl reload-or-restart stunnel")
+
+      server.wait_for_unit("simple-webserver")
+      client.fail("curl --fail --insecure https://server/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "secret handshake" ]]')
+      wrongclient.fail("curl --fail http://localhost/ > out")
+      wrongclient.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+}
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index 814d8d7c0ab3e..408019666da3a 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -74,7 +74,10 @@ let
             services.vaultwarden = {
               enable = true;
               dbBackend = backend;
-              config.rocketPort = 80;
+              config = {
+                rocketAddress = "0.0.0.0";
+                rocketPort = 80;
+              };
             };
 
             networking.firewall.allowedTCPPorts = [ 80 ];
@@ -85,6 +88,8 @@ let
                   {
                     libraries = [ pkgs.python3Packages.selenium ];
                   } ''
+
+                  from selenium.webdriver.common.by import By
                   from selenium.webdriver import Firefox
                   from selenium.webdriver.firefox.options import Options
                   from selenium.webdriver.support.ui import WebDriverWait
@@ -101,40 +106,40 @@ let
 
                   wait.until(EC.title_contains("Create Account"))
 
-                  driver.find_element_by_css_selector('input#email').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#email').send_keys(
                     '${userEmail}'
                   )
-                  driver.find_element_by_css_selector('input#name').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
                     'A Cat'
                   )
-                  driver.find_element_by_css_selector('input#masterPassword').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#masterPassword').send_keys(
                     '${userPassword}'
                   )
-                  driver.find_element_by_css_selector('input#masterPasswordRetype').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#masterPasswordRetype').send_keys(
                     '${userPassword}'
                   )
 
-                  driver.find_element_by_xpath("//button[contains(., 'Submit')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Submit')]").click()
 
                   wait.until_not(EC.title_contains("Create Account"))
 
-                  driver.find_element_by_css_selector('input#masterPassword').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#masterPassword').send_keys(
                     '${userPassword}'
                   )
-                  driver.find_element_by_xpath("//button[contains(., 'Log In')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Log In')]").click()
 
-                  wait.until(EC.title_contains("My Vault"))
+                  wait.until(EC.title_contains("Bitwarden Web Vault"))
 
-                  driver.find_element_by_xpath("//button[contains(., 'Add Item')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Add Item')]").click()
 
-                  driver.find_element_by_css_selector('input#name').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
                     'secrets'
                   )
-                  driver.find_element_by_css_selector('input#loginPassword').send_keys(
+                  driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys(
                     '${storedPassword}'
                   )
 
-                  driver.find_element_by_xpath("//button[contains(., 'Save')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
                 '';
               in
               [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];