summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/console.nix31
-rw-r--r--nixos/modules/config/nsswitch.nix11
-rw-r--r--nixos/modules/config/resolvconf.nix6
-rw-r--r--nixos/modules/config/terminfo.nix26
-rw-r--r--nixos/modules/hardware/all-firmware.nix3
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix5
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix3
-rw-r--r--nixos/modules/hardware/video/nvidia.nix27
-rw-r--r--nixos/modules/hardware/video/webcam/facetimehd.nix5
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix6
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix55
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix6
-rw-r--r--nixos/modules/installer/kexec/kexec-boot.nix51
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64.nix9
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl9
-rw-r--r--nixos/modules/installer/tools/tools.nix7
-rw-r--r--nixos/modules/misc/documentation.nix3
-rw-r--r--nixos/modules/misc/locate.nix2
-rw-r--r--nixos/modules/misc/version.nix40
-rw-r--r--nixos/modules/module-list.nix29
-rw-r--r--nixos/modules/profiles/all-hardware.nix3
-rw-r--r--nixos/modules/programs/_1password-gui.nix68
-rw-r--r--nixos/modules/programs/_1password.nix44
-rw-r--r--nixos/modules/programs/adb.nix3
-rw-r--r--nixos/modules/programs/chromium.nix3
-rw-r--r--nixos/modules/programs/mininet.nix4
-rw-r--r--nixos/modules/programs/nethoscope.nix30
-rw-r--r--nixos/modules/programs/nix-ld.nix12
-rw-r--r--nixos/modules/programs/nncp.nix101
-rw-r--r--nixos/modules/programs/ssh.nix25
-rw-r--r--nixos/modules/programs/ssmtp.nix190
-rw-r--r--nixos/modules/rename.nix6
-rw-r--r--nixos/modules/security/pam.nix110
-rw-r--r--nixos/modules/security/pam_mount.nix84
-rw-r--r--nixos/modules/security/sudo.nix2
-rw-r--r--nixos/modules/services/audio/snapserver.nix22
-rw-r--r--nixos/modules/services/backup/zrepl.nix3
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix4
-rw-r--r--nixos/modules/services/continuous-integration/github-runner.nix10
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix11
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/slave.nix16
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix66
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json17
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix13
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix19
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix12
-rw-r--r--nixos/modules/services/development/jupyter/default.nix1
-rw-r--r--nixos/modules/services/editors/haste.nix86
-rw-r--r--nixos/modules/services/games/factorio.nix9
-rw-r--r--nixos/modules/services/games/minecraft-server.nix2
-rw-r--r--nixos/modules/services/hardware/joycond.nix8
-rw-r--r--nixos/modules/services/hardware/udev.nix123
-rw-r--r--nixos/modules/services/hardware/udisks2.nix39
-rw-r--r--nixos/modules/services/hardware/usbrelayd.nix44
-rw-r--r--nixos/modules/services/logging/graylog.nix2
-rw-r--r--nixos/modules/services/logging/klogd.nix41
-rw-r--r--nixos/modules/services/logging/logrotate.nix278
-rw-r--r--nixos/modules/services/mail/mailman.nix21
-rw-r--r--nixos/modules/services/mail/postfix.nix39
-rw-r--r--nixos/modules/services/matrix/matrix-synapse.nix4
-rw-r--r--nixos/modules/services/matrix/matrix-synapse.xml6
-rw-r--r--nixos/modules/services/misc/autorandr.nix310
-rw-r--r--nixos/modules/services/misc/dendrite.nix14
-rw-r--r--nixos/modules/services/misc/etebase-server.nix2
-rw-r--r--nixos/modules/services/misc/ethminer.nix8
-rw-r--r--nixos/modules/services/misc/gitea.nix27
-rw-r--r--nixos/modules/services/misc/gitit.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix20
-rw-r--r--nixos/modules/services/misc/moonraker.nix40
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix20
-rw-r--r--nixos/modules/services/misc/nix-gc.nix12
-rw-r--r--nixos/modules/services/misc/nix-optimise.nix10
-rw-r--r--nixos/modules/services/misc/paperless.nix (renamed from nixos/modules/services/misc/paperless-ng.nix)104
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix2
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix16
-rw-r--r--nixos/modules/services/misc/taskserver/helper-tool.py10
-rw-r--r--nixos/modules/services/monitoring/collectd.nix56
-rw-r--r--nixos/modules/services/monitoring/grafana.nix5
-rw-r--r--nixos/modules/services/monitoring/nagios.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml21
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bird.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix5
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix48
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix1
-rw-r--r--nixos/modules/services/networking/bird.nix3
-rw-r--r--nixos/modules/services/networking/consul.nix38
-rw-r--r--nixos/modules/services/networking/create_ap.nix50
-rw-r--r--nixos/modules/services/networking/dhcpd.nix8
-rw-r--r--nixos/modules/services/networking/envoy.nix84
-rw-r--r--nixos/modules/services/networking/headscale.nix2
-rw-r--r--nixos/modules/services/networking/https-dns-proxy.nix128
-rw-r--r--nixos/modules/services/networking/iwd.nix21
-rw-r--r--nixos/modules/services/networking/kea.nix82
-rw-r--r--nixos/modules/services/networking/lxd-image-server.nix18
-rw-r--r--nixos/modules/services/networking/mozillavpn.nix19
-rw-r--r--nixos/modules/services/networking/nbd.nix43
-rw-r--r--nixos/modules/services/networking/ncdns.nix4
-rw-r--r--nixos/modules/services/networking/networkmanager.nix109
-rw-r--r--nixos/modules/services/networking/openconnect.nix137
-rw-r--r--nixos/modules/services/networking/openfire.nix56
-rw-r--r--nixos/modules/services/networking/pdns-recursor.nix19
-rw-r--r--nixos/modules/services/networking/powerdns.nix4
-rw-r--r--nixos/modules/services/networking/shellhub-agent.nix43
-rw-r--r--nixos/modules/services/networking/squid.nix17
-rw-r--r--nixos/modules/services/networking/supplicant.nix2
-rw-r--r--nixos/modules/services/networking/syncplay.nix18
-rw-r--r--nixos/modules/services/networking/tailscale.nix10
-rw-r--r--nixos/modules/services/networking/wg-quick.nix9
-rw-r--r--nixos/modules/services/networking/zeronet.nix2
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix13
-rw-r--r--nixos/modules/services/security/sslmate-agent.nix32
-rw-r--r--nixos/modules/services/security/tor.nix5
-rw-r--r--nixos/modules/services/system/earlyoom.nix98
-rw-r--r--nixos/modules/services/system/nscd.nix25
-rw-r--r--nixos/modules/services/ttys/kmscon.nix29
-rw-r--r--nixos/modules/services/video/unifi-video.nix168
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix1
-rw-r--r--nixos/modules/services/web-apps/discourse.nix12
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix2
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix859
-rw-r--r--nixos/modules/services/web-apps/keycloak.xml142
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix4
-rw-r--r--nixos/modules/services/web-apps/netbox.nix265
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix75
-rw-r--r--nixos/modules/services/web-apps/nifi.nix318
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix21
-rw-r--r--nixos/modules/services/web-servers/hydron.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix46
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix15
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix112
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix36
-rw-r--r--nixos/modules/services/x11/desktop-managers/none.nix49
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.xml6
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix9
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix15
-rw-r--r--nixos/modules/services/x11/xserver.nix13
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl644
-rw-r--r--nixos/modules/system/activation/top-level.nix17
-rw-r--r--nixos/modules/system/boot/kernel.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix26
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl8
-rw-r--r--nixos/modules/system/boot/luksroot.nix80
-rw-r--r--nixos/modules/system/boot/networkd.nix193
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh3
-rw-r--r--nixos/modules/system/boot/stage-1.nix47
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh164
-rw-r--r--nixos/modules/system/boot/stage-2.nix41
-rw-r--r--nixos/modules/system/boot/systemd.nix536
-rw-r--r--nixos/modules/system/boot/systemd/coredump.nix57
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix509
-rw-r--r--nixos/modules/system/boot/systemd/journald.nix131
-rw-r--r--nixos/modules/system/boot/systemd/logind.nix114
-rw-r--r--nixos/modules/system/boot/systemd/nspawn.nix (renamed from nixos/modules/system/boot/systemd-nspawn.nix)8
-rw-r--r--nixos/modules/system/boot/systemd/shutdown.nix32
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix120
-rw-r--r--nixos/modules/system/boot/systemd/user.nix153
-rw-r--r--nixos/modules/system/boot/timesyncd.nix22
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix41
-rw-r--r--nixos/modules/tasks/bcache.nix22
-rw-r--r--nixos/modules/tasks/filesystems.nix31
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix6
-rw-r--r--nixos/modules/tasks/filesystems/cifs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/ext.nix14
-rw-r--r--nixos/modules/tasks/filesystems/f2fs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/jfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/reiserfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/unionfs-fuse.nix15
-rw-r--r--nixos/modules/tasks/filesystems/vfat.nix2
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix4
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix37
-rw-r--r--nixos/modules/tasks/lvm.nix61
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces.nix34
-rw-r--r--nixos/modules/tasks/swraid.nix44
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix14
-rw-r--r--nixos/modules/virtualisation/azure-common.nix6
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix4
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix37
-rw-r--r--nixos/modules/virtualisation/openstack-config.nix46
-rw-r--r--nixos/modules/virtualisation/openstack-metadata-fetcher.nix6
-rw-r--r--nixos/modules/virtualisation/openstack-options.nix71
-rw-r--r--nixos/modules/virtualisation/podman/default.nix5
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix39
-rw-r--r--nixos/modules/virtualisation/waydroid.nix6
194 files changed, 6668 insertions, 2801 deletions
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 168bebd8d06..5b07901f990 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -12,7 +12,7 @@ let
 
   optimizedKeymap = pkgs.runCommand "keymap" {
     nativeBuildInputs = [ pkgs.buildPackages.kbd ];
-    LOADKEYS_KEYMAP_PATH = "${consoleEnv}/share/keymaps/**";
+    LOADKEYS_KEYMAP_PATH = "${consoleEnv pkgs.kbd}/share/keymaps/**";
     preferLocalBuild = true;
   } ''
     loadkeys -b ${optionalString isUnicode "-u"} "${cfg.keyMap}" > $out
@@ -24,9 +24,9 @@ let
     FONT=${cfg.font}
   '';
 
-  consoleEnv = pkgs.buildEnv {
+  consoleEnv = kbd: pkgs.buildEnv {
     name = "console-env";
-    paths = [ pkgs.kbd ] ++ cfg.packages;
+    paths = [ kbd ] ++ cfg.packages;
     pathsToLink = [
       "/share/consolefonts"
       "/share/consoletrans"
@@ -136,9 +136,9 @@ in
         # virtual consoles.
         environment.etc."vconsole.conf".source = vconsoleConf;
         # Provide kbd with additional packages.
-        environment.etc.kbd.source = "${consoleEnv}/share";
+        environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
 
-        boot.initrd.preLVMCommands = mkBefore ''
+        boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ''
           kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
           printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
           loadkmap < ${optimizedKeymap}
@@ -146,12 +146,23 @@ in
           ${optionalString cfg.earlySetup ''
             setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
           ''}
-        '';
+        '');
+
+        boot.initrd.systemd.contents = {
+          "/etc/kbd".source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share";
+          "/etc/vconsole.conf".source = vconsoleConf;
+        };
+        boot.initrd.systemd.storePaths = [
+          "${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
+        ];
 
         systemd.services.reload-systemd-vconsole-setup =
           { description = "Reset console on configuration changes";
             wantedBy = [ "multi-user.target" ];
-            restartTriggers = [ vconsoleConf consoleEnv ];
+            restartTriggers = [ vconsoleConf (consoleEnv pkgs.kbd) ];
             reloadIfChanged = true;
             serviceConfig =
               { RemainAfterExit = true;
@@ -175,7 +186,7 @@ in
           ${if substring 0 1 cfg.font == "/" then ''
             font="${cfg.font}"
           '' else ''
-            font="$(echo ${consoleEnv}/share/consolefonts/${cfg.font}.*)"
+            font="$(echo ${consoleEnv pkgs.kbd}/share/consolefonts/${cfg.font}.*)"
           ''}
           if [[ $font == *.gz ]]; then
             gzip -cd $font > $out/share/consolefonts/font.psf
@@ -183,6 +194,10 @@ in
             cp -L $font $out/share/consolefonts/font.psf
           fi
         '';
+        assertions = [{
+          assertion = !config.boot.initrd.systemd.enable;
+          message = "console.earlySetup is implied by systemd stage 1";
+        }];
       })
     ]))
   ];
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index 91a36cef10e..e494ff5f74d 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -95,11 +95,14 @@ with lib;
   config = {
     assertions = [
       {
-        # Prevent users from disabling nscd, with nssModules being set.
-        # If disabling nscd is really necessary, it's still possible to opt out
-        # by forcing config.system.nssModules to [].
         assertion = config.system.nssModules.path != "" -> config.services.nscd.enable;
-        message = "Loading NSS modules from system.nssModules (${config.system.nssModules.path}), requires services.nscd.enable being set to true.";
+        message = ''
+          Loading NSS modules from system.nssModules (${config.system.nssModules.path}),
+          requires services.nscd.enable being set to true.
+
+          If disabling nscd is really necessary, it is possible to disable loading NSS modules
+          by setting `system.nssModules = lib.mkForce [];` in your configuration.nix.
+        '';
       }
     ];
 
diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix
index cd0ed491383..4499481811f 100644
--- a/nixos/modules/config/resolvconf.nix
+++ b/nixos/modules/config/resolvconf.nix
@@ -47,8 +47,8 @@ in
 
       enable = mkOption {
         type = types.bool;
-        default = false;
-        internal = true;
+        default = !(config.environment.etc ? "resolv.conf");
+        defaultText = literalExpression ''!(config.environment.etc ? "resolv.conf")'';
         description = ''
           DNS configuration is managed by resolvconf.
         '';
@@ -110,8 +110,6 @@ in
 
   config = mkMerge [
     {
-      networking.resolvconf.enable = !(config.environment.etc ? "resolv.conf");
-
       environment.etc."resolvconf.conf".text =
         if !cfg.enable then
           # Force-stop any attempts to use resolvconf
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index 1396640af67..693404a429c 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -1,9 +1,33 @@
 # This module manages the terminfo database
 # and its integration in the system.
-{ config, ... }:
+{ config, lib, pkgs, ... }:
+
+with lib;
+
 {
+
+  options.environment.enableAllTerminfo = with lib; mkOption {
+    default = false;
+    type = types.bool;
+    description = ''
+      Whether to install all terminfo outputs
+    '';
+  };
+
   config = {
 
+    # can be generated with: filter (drv: (builtins.tryEval (drv ? terminfo)).value) (attrValues pkgs)
+    environment.systemPackages = mkIf config.environment.enableAllTerminfo (map (x: x.terminfo) (with pkgs; [
+      alacritty
+      foot
+      kitty
+      mtm
+      rxvt-unicode-unwrapped
+      rxvt-unicode-unwrapped-emoji
+      termite
+      wezterm
+    ]));
+
     environment.pathsToLink = [
       "/share/terminfo"
     ];
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 5b60b17312f..74678416a15 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -27,7 +27,8 @@ in {
     };
 
     hardware.enableRedistributableFirmware = mkOption {
-      default = false;
+      default = config.hardware.enableAllFirmware;
+      defaultText = lib.literalExpression "config.hardware.enableAllFirmware";
       type = types.bool;
       description = ''
         Turn on this option if you want to enable all the firmware with a license allowing redistribution.
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index c4977e3fd70..fa6f0b8fc84 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -8,7 +8,10 @@ let
     version = "2.40-13.0";
 
     src = pkgs.fetchurl {
-      url = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${pname}-${version}_amd64.deb";
+      urls = [
+        "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${pname}-${version}_amd64.deb"
+        "http://apt.netangels.net/pool/main/h/hpssacli/${pname}-${version}_amd64.deb"
+      ];
       sha256 = "11w7fwk93lmfw0yya4jpjwdmgjimqxx6412sqa166g1pz4jil4sw";
     };
 
diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix
index d784befc9b8..299a30b0629 100644
--- a/nixos/modules/hardware/video/amdgpu-pro.nix
+++ b/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -51,9 +51,10 @@ in
       (isYes "KALLSYMS_ALL")
     ];
 
-    boot.initrd.extraUdevRulesCommands = ''
+    boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       cp -v ${package}/etc/udev/rules.d/*.rules $out/
     '';
+    boot.initrd.services.udev.packages = [ package ];
 
     environment.systemPackages =
       [ package.vulkan ] ++
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index a81220a92a1..6899eb4e196 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -24,6 +24,7 @@ let
   primeEnabled = syncCfg.enable || offloadCfg.enable;
   nvidiaPersistencedEnabled =  cfg.nvidiaPersistenced;
   nvidiaSettings = cfg.nvidiaSettings;
+  busIDType = types.strMatching "([[:print:]]+\:[0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
 in
 
 {
@@ -68,7 +69,7 @@ in
     };
 
     hardware.nvidia.prime.nvidiaBusId = mkOption {
-      type = types.str;
+      type = busIDType;
       default = "";
       example = "PCI:1:0:0";
       description = ''
@@ -78,7 +79,7 @@ in
     };
 
     hardware.nvidia.prime.intelBusId = mkOption {
-      type = types.str;
+      type = busIDType;
       default = "";
       example = "PCI:0:2:0";
       description = ''
@@ -88,7 +89,7 @@ in
     };
 
     hardware.nvidia.prime.amdgpuBusId = mkOption {
-      type = types.str;
+      type = busIDType;
       default = "";
       example = "PCI:4:0:0";
       description = ''
@@ -244,7 +245,7 @@ in
       modules = optional (igpuDriver == "amdgpu") [ pkgs.xorg.xf86videoamdgpu ];
       deviceSection = ''
         BusID "${igpuBusId}"
-        ${optionalString syncCfg.enable ''Option "AccelMethod" "none"''}
+        ${optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''Option "AccelMethod" "none"''}
       '';
     } ++ singleton {
       name = "nvidia";
@@ -269,9 +270,15 @@ in
       Option "AllowNVIDIAGPUScreens"
     '';
 
-    services.xserver.displayManager.setupCommands = optionalString syncCfg.enable ''
+    services.xserver.displayManager.setupCommands = let
+      sinkGpuProviderName = if igpuDriver == "amdgpu" then
+        # find the name of the provider if amdgpu
+        "`${pkgs.xorg.xrandr}/bin/xrandr --listproviders | ${pkgs.gnugrep}/bin/grep -i AMD | ${pkgs.gnused}/bin/sed -n 's/^.*name://p'`"
+      else
+        igpuDriver;
+    in optionalString syncCfg.enable ''
       # Added by nvidia configuration module for Optimus/PRIME.
-      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource ${igpuDriver} NVIDIA-0
+      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource "${sinkGpuProviderName}" NVIDIA-0
       ${pkgs.xorg.xrandr}/bin/xrandr --auto
     '';
 
@@ -283,14 +290,14 @@ in
     environment.etc."egl/egl_external_platform.d".source =
       "/run/opengl-driver/share/egl/egl_external_platform.d/";
 
-    hardware.opengl.package = mkIf (!offloadCfg.enable) nvidia_x11.out;
-    hardware.opengl.package32 = mkIf (!offloadCfg.enable) nvidia_x11.lib32;
     hardware.opengl.extraPackages = [
+      nvidia_x11.out
       pkgs.nvidia-vaapi-driver
-    ] ++ optional offloadCfg.enable nvidia_x11.out;
+    ];
     hardware.opengl.extraPackages32 = [
+      nvidia_x11.lib32
       pkgs.pkgsi686Linux.nvidia-vaapi-driver
-    ] ++ optional offloadCfg.enable nvidia_x11.lib32;
+    ];
 
     environment.systemPackages = [ nvidia_x11.bin ]
       ++ optionals cfg.nvidiaSettings [ nvidia_x11.settings ]
diff --git a/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixos/modules/hardware/video/webcam/facetimehd.nix
index d311f600c31..b13f103350e 100644
--- a/nixos/modules/hardware/video/webcam/facetimehd.nix
+++ b/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -16,11 +16,6 @@ in
 
   config = mkIf cfg.enable {
 
-    assertions = singleton {
-      assertion = versionAtLeast kernelPackages.kernel.version "3.19";
-      message = "facetimehd is not supported for kernels older than 3.19";
-    };
-
     boot.kernelModules = [ "facetimehd" ];
 
     boot.blacklistedKernelModules = [ "bdc_pci" ];
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index 414aabbbaa7..6fea28e2234 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -28,11 +28,5 @@ in {
       QT_IM_MODULE = "fcitx";
       XMODIFIERS = "@im=fcitx";
     };
-
-    systemd.user.services.fcitx5-daemon = {
-      enable = true;
-      script = "${fcitx5Package}/bin/fcitx5";
-      wantedBy = [ "graphical-session.target" ];
-    };
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 3ff1b3d670e..860e240b43d 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -91,29 +91,9 @@ let
     SERIAL 0 115200
     TIMEOUT ${builtins.toString syslinuxTimeout}
     UI vesamenu.c32
-    MENU TITLE NixOS
     MENU BACKGROUND /isolinux/background.png
-    MENU RESOLUTION 800 600
-    MENU CLEAR
-    MENU ROWS 6
-    MENU CMDLINEROW -4
-    MENU TIMEOUTROW -3
-    MENU TABMSGROW  -2
-    MENU HELPMSGROW -1
-    MENU HELPMSGENDROW -1
-    MENU MARGIN 0
-
-    #                                FG:AARRGGBB  BG:AARRGGBB   shadow
-    MENU COLOR BORDER       30;44      #00000000    #00000000   none
-    MENU COLOR SCREEN       37;40      #FF000000    #00E2E8FF   none
-    MENU COLOR TABMSG       31;40      #80000000    #00000000   none
-    MENU COLOR TIMEOUT      1;37;40    #FF000000    #00000000   none
-    MENU COLOR TIMEOUT_MSG  37;40      #FF000000    #00000000   none
-    MENU COLOR CMDMARK      1;36;40    #FF000000    #00000000   none
-    MENU COLOR CMDLINE      37;40      #FF000000    #00000000   none
-    MENU COLOR TITLE        1;36;44    #00000000    #00000000   none
-    MENU COLOR UNSEL        37;44      #FF000000    #00000000   none
-    MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
+
+    ${config.isoImage.syslinuxTheme}
 
     DEFAULT boot
 
@@ -601,6 +581,37 @@ in
       '';
     };
 
+    isoImage.syslinuxTheme = mkOption {
+      default = ''
+        MENU TITLE NixOS
+        MENU RESOLUTION 800 600
+        MENU CLEAR
+        MENU ROWS 6
+        MENU CMDLINEROW -4
+        MENU TIMEOUTROW -3
+        MENU TABMSGROW  -2
+        MENU HELPMSGROW -1
+        MENU HELPMSGENDROW -1
+        MENU MARGIN 0
+
+        #                                FG:AARRGGBB  BG:AARRGGBB   shadow
+        MENU COLOR BORDER       30;44      #00000000    #00000000   none
+        MENU COLOR SCREEN       37;40      #FF000000    #00E2E8FF   none
+        MENU COLOR TABMSG       31;40      #80000000    #00000000   none
+        MENU COLOR TIMEOUT      1;37;40    #FF000000    #00000000   none
+        MENU COLOR TIMEOUT_MSG  37;40      #FF000000    #00000000   none
+        MENU COLOR CMDMARK      1;36;40    #FF000000    #00000000   none
+        MENU COLOR CMDLINE      37;40      #FF000000    #00000000   none
+        MENU COLOR TITLE        1;36;44    #00000000    #00000000   none
+        MENU COLOR UNSEL        37;44      #FF000000    #00000000   none
+        MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
+      '';
+      type = types.str;
+      description = ''
+        The syslinux theme used for BIOS boot.
+      '';
+    };
+
     isoImage.appendToMenuLabel = mkOption {
       default = " Installer";
       example = " Live System";
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 458e313a3f7..329bd329dc1 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -87,19 +87,19 @@ in
   boot.initrd.availableKernelModules =
     [ "mvsdio" "reiserfs" "ext3" "ums-cypress" "rtc_mv" "ext4" ];
 
-  boot.postBootCommands =
+  boot.postBootCommands = lib.mkIf (!boot.initrd.systemd.enable)
     ''
       mkdir -p /mnt
 
       cp ${dummyConfiguration} /etc/nixos/configuration.nix
     '';
 
-  boot.initrd.extraUtilsCommands =
+  boot.initrd.extraUtilsCommands = lib.mkIf (!boot.initrd.systemd.enable)
     ''
       copy_bin_and_libs ${pkgs.util-linux}/sbin/hwclock
     '';
 
-  boot.initrd.postDeviceCommands =
+  boot.initrd.postDeviceCommands = lib.mkIf (!boot.initrd.systemd.enable)
     ''
       hwclock -s
     '';
diff --git a/nixos/modules/installer/kexec/kexec-boot.nix b/nixos/modules/installer/kexec/kexec-boot.nix
new file mode 100644
index 00000000000..2d062214efc
--- /dev/null
+++ b/nixos/modules/installer/kexec/kexec-boot.nix
@@ -0,0 +1,51 @@
+# This module exposes a config.system.build.kexecBoot attribute,
+# which returns a directory with kernel, initrd and a shell script
+# running the necessary kexec commands.
+
+# It's meant to be scp'ed to a machine with working ssh and kexec binary
+# installed.
+
+# This is useful for (cloud) providers where you can't boot a custom image, but
+# get some Debian or Ubuntu installation.
+
+{ pkgs
+, modulesPath
+, config
+, ...
+}:
+{
+  imports = [
+    (modulesPath + "/installer/netboot/netboot-minimal.nix")
+  ];
+
+  config = {
+    system.build.kexecBoot =
+      let
+        kexecScript = pkgs.writeScript "kexec-boot" ''
+          #!/usr/bin/env bash
+          if ! kexec -v >/dev/null 2>&1; then
+            echo "kexec not found: please install kexec-tools" 2>&1
+            exit 1
+          fi
+          SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+          kexec --load ''${SCRIPT_DIR}/bzImage \
+            --initrd=''${SCRIPT_DIR}/initrd.gz \
+            --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
+          kexec -e
+        ''; in
+      pkgs.linkFarm "kexec-tree" [
+        {
+          name = "initrd.gz";
+          path = "${config.system.build.netbootRamdisk}/initrd";
+        }
+        {
+          name = "bzImage";
+          path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
+        }
+        {
+          name = "kexec-boot";
+          path = kexecScript;
+        }
+      ];
+  };
+}
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64.nix b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
index 321793882f4..cf01005fdc8 100644
--- a/nixos/modules/installer/sd-card/sd-image-aarch64.nix
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
@@ -39,6 +39,12 @@
         # Supported in newer board revisions
         arm_boost=1
 
+        [cm4]
+        # Enable host mode on the 2711 built-in XHCI USB controller.
+        # This line should be removed if the legacy DWC2 controller is required
+        # (e.g. for USB device mode) or if USB support is not required.
+        otg_mode=1
+
         [all]
         # Boot in 64-bit mode.
         arm_64bit=1
@@ -65,6 +71,9 @@
         cp ${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin firmware/u-boot-rpi4.bin
         cp ${pkgs.raspberrypi-armstubs}/armstub8-gic.bin firmware/armstub8-gic.bin
         cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-4-b.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-400.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-cm4.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2711-rpi-cm4s.dtb firmware/
       '';
     populateRootCommands = ''
       mkdir -p ./files/boot
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index dfafda77cb5..1707935ad5b 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/0n2wfvi1i3fg97cjc54wslvk0804y0sn-nix-2.7.0";
-  i686-linux = "/nix/store/4p27c1k9z99pli6x8cxfph20yfyzn9nh-nix-2.7.0";
-  aarch64-linux = "/nix/store/r9yr8ijsb0gi9r7y92y3yzyld59yp0kj-nix-2.7.0";
-  x86_64-darwin = "/nix/store/hyfj5imsd0c4amlcjpf8l6w4q2draaj3-nix-2.7.0";
-  aarch64-darwin = "/nix/store/9l96qllhbb6xrsjaai76dn74ap7rq92n-nix-2.7.0";
+  x86_64-linux = "/nix/store/yx36yzxpw1hn4fz8iyf1rfyd56jg3yf4-nix-2.8.0";
+  i686-linux = "/nix/store/c0hg806zvwg800qbszzj8ff4a224kjgf-nix-2.8.0";
+  aarch64-linux = "/nix/store/wic2832ll53q392r2wks4xr2nrk7p8p5-nix-2.8.0";
+  x86_64-darwin = "/nix/store/5yqdvnkmkrhl36xh0qy31pymdphjimdd-nix-2.8.0";
+  aarch64-darwin = "/nix/store/izc9592szrnpv8n86hr88bhpyc9g6b4s-nix-2.8.0";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 57aef50a0f6..fb5d3ba4732 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -51,7 +51,9 @@ for (my $n = 0; $n < scalar @ARGV; $n++) {
         $n++;
         $rootDir = $ARGV[$n];
         die "$0: ‘--root’ requires an argument\n" unless defined $rootDir;
+        die "$0: no need to specify `/` with `--root`, it is the default\n" if $rootDir eq "/";
         $rootDir =~ s/\/*$//; # remove trailing slashes
+        $rootDir = File::Spec->rel2abs($rootDir); # resolve absolute path
     }
     elsif ($arg eq "--force") {
         $force = 1;
@@ -616,7 +618,12 @@ EOF
 if ($showHardwareConfig) {
     print STDOUT $hwConfig;
 } else {
-    $outDir = "$rootDir$outDir";
+    if ($outDir eq "/etc/nixos") {
+        $outDir = "$rootDir$outDir";
+    } else {
+        $outDir = File::Spec->rel2abs($outDir);
+        $outDir =~ s/\/*$//; # remove trailing slashes
+    }
 
     my $fn = "$outDir/hardware-configuration.nix";
     print STDERR "writing $fn...\n";
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 71aaf7f253d..bf5ec0f9690 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -117,7 +117,7 @@ in
     '';
   };
 
-  config = lib.mkIf (!config.system.disableInstallerTools) {
+  config = lib.mkIf (config.nix.enable && !config.system.disableInstallerTools) {
 
     system.nixos-generate-config.configuration = mkDefault ''
       # Edit this configuration file to define what should be installed on
@@ -206,6 +206,11 @@ in
         # Or disable the firewall altogether.
         # networking.firewall.enable = false;
 
+        # Copy the NixOS configuration file and link it from the resulting system
+        # (/run/current-system/configuration.nix). This is useful in case you
+        # accidentally delete configuration.nix.
+        # system.copySystemConfiguration = true;
+
         # This value determines the NixOS release from which the default
         # settings for stateful data, like file locations and database versions
         # on your system were taken. It‘s perfectly fine and recommended to leave
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 9304c307af2..8e28d3336fa 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -64,7 +64,8 @@ let
         filter =
           builtins.filterSource
             (n: t:
-              (t == "directory" -> baseNameOf n != "tests")
+              cleanSourceFilter n t
+              && (t == "directory" -> baseNameOf n != "tests")
               && (t == "file" -> hasSuffix ".nix" n)
             );
       in
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index 204a8914300..192c9ec413c 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -27,7 +27,7 @@ in
 
     locate = mkOption {
       type = package;
-      default = pkgs.findutils;
+      default = pkgs.findutils.locate;
       defaultText = literalExpression "pkgs.findutils";
       example = literalExpression "pkgs.mlocate";
       description = ''
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index d825f4beb30..931201ade29 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -15,6 +15,26 @@ let
       mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
     );
 
+  osReleaseContents = {
+    NAME = "NixOS";
+    ID = "nixos";
+    VERSION = "${cfg.release} (${cfg.codeName})";
+    VERSION_CODENAME = toLower cfg.codeName;
+    VERSION_ID = cfg.release;
+    BUILD_ID = cfg.version;
+    PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+    LOGO = "nix-snowflake";
+    HOME_URL = "https://nixos.org/";
+    DOCUMENTATION_URL = "https://nixos.org/learn.html";
+    SUPPORT_URL = "https://nixos.org/community.html";
+    BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+  };
+
+  initrdReleaseContents = osReleaseContents // {
+    PRETTY_NAME = "${osReleaseContents.PRETTY_NAME} (Initrd)";
+  };
+  initrdRelease = pkgs.writeText "initrd-release" (attrsToText initrdReleaseContents);
+
 in
 {
   imports = [
@@ -119,20 +139,12 @@ in
         DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
       };
 
-      "os-release".text = attrsToText {
-        NAME = "NixOS";
-        ID = "nixos";
-        VERSION = "${cfg.release} (${cfg.codeName})";
-        VERSION_CODENAME = toLower cfg.codeName;
-        VERSION_ID = cfg.release;
-        BUILD_ID = cfg.version;
-        PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
-        LOGO = "nix-snowflake";
-        HOME_URL = "https://nixos.org/";
-        DOCUMENTATION_URL = "https://nixos.org/learn.html";
-        SUPPORT_URL = "https://nixos.org/community.html";
-        BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
-      };
+      "os-release".text = attrsToText osReleaseContents;
+    };
+
+    boot.initrd.systemd.contents = {
+      "/etc/os-release".source = initrdRelease;
+      "/etc/initrd-release".source = initrdRelease;
     };
   };
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 68f9c6c1227..dcd9bb8aff1 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -118,6 +118,8 @@
   ./misc/version.nix
   ./misc/wordlist.nix
   ./misc/nixops-autoluks.nix
+  ./programs/_1password.nix
+  ./programs/_1password-gui.nix
   ./programs/adb.nix
   ./programs/appgate-sdp.nix
   ./programs/atop.nix
@@ -181,8 +183,11 @@
   ./programs/mtr.nix
   ./programs/nano.nix
   ./programs/nbd.nix
+  ./programs/nix-ld.nix
   ./programs/neovim.nix
+  ./programs/nethoscope.nix
   ./programs/nm-applet.nix
+  ./programs/nncp.nix
   ./programs/npm.nix
   ./programs/noisetorch.nix
   ./programs/oblogout.nix
@@ -200,7 +205,6 @@
   ./programs/spacefm.nix
   ./programs/singularity.nix
   ./programs/ssh.nix
-  ./programs/ssmtp.nix
   ./programs/sysdig.nix
   ./programs/systemtap.nix
   ./programs/starship.nix
@@ -405,6 +409,7 @@
   ./services/display-managers/greetd.nix
   ./services/editors/emacs.nix
   ./services/editors/infinoted.nix
+  ./services/editors/haste.nix
   ./services/finance/odoo.nix
   ./services/games/asf.nix
   ./services/games/crossfire-server.nix
@@ -454,6 +459,7 @@
   ./services/hardware/udisks2.nix
   ./services/hardware/upower.nix
   ./services/hardware/usbmuxd.nix
+  ./services/hardware/usbrelayd.nix
   ./services/hardware/thermald.nix
   ./services/hardware/undervolt.nix
   ./services/hardware/vdr.nix
@@ -592,7 +598,7 @@
   ./services/misc/osrm.nix
   ./services/misc/owncast.nix
   ./services/misc/packagekit.nix
-  ./services/misc/paperless-ng.nix
+  ./services/misc/paperless.nix
   ./services/misc/parsoid.nix
   ./services/misc/plex.nix
   ./services/misc/plikd.nix
@@ -735,6 +741,7 @@
   ./services/networking/coredns.nix
   ./services/networking/corerad.nix
   ./services/networking/coturn.nix
+  ./services/networking/create_ap.nix
   ./services/networking/croc.nix
   ./services/networking/dante.nix
   ./services/networking/ddclient.nix
@@ -749,6 +756,7 @@
   ./services/networking/ncdns.nix
   ./services/networking/nomad.nix
   ./services/networking/ejabberd.nix
+  ./services/networking/envoy.nix
   ./services/networking/epmd.nix
   ./services/networking/ergo.nix
   ./services/networking/ergochat.nix
@@ -776,6 +784,7 @@
   ./services/networking/headscale.nix
   ./services/networking/hostapd.nix
   ./services/networking/htpdate.nix
+  ./services/networking/https-dns-proxy.nix
   ./services/networking/hylafax/default.nix
   ./services/networking/i2pd.nix
   ./services/networking/i2p.nix
@@ -810,6 +819,7 @@
   ./services/networking/mosquitto.nix
   ./services/networking/monero.nix
   ./services/networking/morty.nix
+  ./services/networking/mozillavpn.nix
   ./services/networking/miredo.nix
   ./services/networking/mstpd.nix
   ./services/networking/mtprotoproxy.nix
@@ -845,7 +855,7 @@
   ./services/networking/ofono.nix
   ./services/networking/oidentd.nix
   ./services/networking/onedrive.nix
-  ./services/networking/openfire.nix
+  ./services/networking/openconnect.nix
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
   ./services/networking/owamp.nix
@@ -972,6 +982,7 @@
   ./services/security/shibboleth-sp.nix
   ./services/security/sks.nix
   ./services/security/sshguard.nix
+  ./services/security/sslmate-agent.nix
   ./services/security/step-ca.nix
   ./services/security/tor.nix
   ./services/security/torify.nix
@@ -1043,8 +1054,10 @@
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/moodle.nix
+  ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
+  ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
   ./services/web-apps/pict-rs.nix
   ./services/web-apps/peertube.nix
@@ -1168,7 +1181,14 @@
   ./system/boot/stage-1.nix
   ./system/boot/stage-2.nix
   ./system/boot/systemd.nix
-  ./system/boot/systemd-nspawn.nix
+  ./system/boot/systemd/coredump.nix
+  ./system/boot/systemd/journald.nix
+  ./system/boot/systemd/logind.nix
+  ./system/boot/systemd/nspawn.nix
+  ./system/boot/systemd/shutdown.nix
+  ./system/boot/systemd/tmpfiles.nix
+  ./system/boot/systemd/user.nix
+  ./system/boot/systemd/initrd.nix
   ./system/boot/timesyncd.nix
   ./system/boot/tmp.nix
   ./system/etc/etc-activation.nix
@@ -1222,6 +1242,7 @@
   ./virtualisation/amazon-options.nix
   ./virtualisation/hyperv-guest.nix
   ./virtualisation/kvmgt.nix
+  ./virtualisation/openstack-options.nix
   ./virtualisation/openvswitch.nix
   ./virtualisation/parallels-guest.nix
   ./virtualisation/podman/default.nix
diff --git a/nixos/modules/profiles/all-hardware.nix b/nixos/modules/profiles/all-hardware.nix
index 25f68123a1d..8347453d403 100644
--- a/nixos/modules/profiles/all-hardware.nix
+++ b/nixos/modules/profiles/all-hardware.nix
@@ -40,6 +40,9 @@ in
       # SD cards.
       "sdhci_pci"
 
+      # NVMe drives
+      "nvme"
+
       # Firewire support.  Not tested.
       "ohci1394" "sbp2"
 
diff --git a/nixos/modules/programs/_1password-gui.nix b/nixos/modules/programs/_1password-gui.nix
new file mode 100644
index 00000000000..42f6a0b5225
--- /dev/null
+++ b/nixos/modules/programs/_1password-gui.nix
@@ -0,0 +1,68 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs._1password-gui;
+
+in
+{
+  options = {
+    programs._1password-gui = {
+      enable = mkEnableOption "the 1Password GUI application";
+
+      gid = mkOption {
+        type = types.addCheck types.int (x: x >= 1000);
+        example = literalExpression "5000";
+        description = ''
+          The gid to assign to the onepassword group, which is needed for browser integration.
+          It must be 1000 or greater.
+        '';
+      };
+
+      polkitPolicyOwners = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = literalExpression ''["user1" "user2" "user3"]'';
+        description = ''
+          A list of users who should be able to integrate 1Password with polkit-based authentication mechanisms.
+        '';
+      };
+
+      package = mkPackageOption pkgs "1Password GUI" {
+        default = [ "_1password-gui" ];
+      };
+    };
+  };
+
+  config =
+    let
+      package = cfg.package.override {
+        polkitPolicyOwners = cfg.polkitPolicyOwners;
+      };
+    in
+    mkIf cfg.enable {
+      environment.systemPackages = [ package ];
+      users.groups.onepassword.gid = cfg.gid;
+
+      security.wrappers = {
+        "1Password-BrowserSupport" = {
+          source = "${package}/share/1password/1Password-BrowserSupport";
+          owner = "root";
+          group = "onepassword";
+          setuid = false;
+          setgid = true;
+        };
+
+        "1Password-KeyringHelper" = {
+          source = "${package}/share/1password/1Password-KeyringHelper";
+          owner = "root";
+          group = "onepassword";
+          setuid = true;
+          setgid = true;
+        };
+      };
+
+    };
+}
diff --git a/nixos/modules/programs/_1password.nix b/nixos/modules/programs/_1password.nix
new file mode 100644
index 00000000000..547c12867a9
--- /dev/null
+++ b/nixos/modules/programs/_1password.nix
@@ -0,0 +1,44 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs._1password;
+
+in
+{
+  options = {
+    programs._1password = {
+      enable = mkEnableOption "the 1Password CLI tool";
+
+      gid = mkOption {
+        type = types.addCheck types.int (x: x >= 1000);
+        example = literalExpression "5001";
+        description = ''
+          The gid to assign to the onepassword-cli group, which is needed for integration with the 1Password GUI.
+          It must be 1000 or greater.
+        '';
+      };
+
+      package = mkPackageOption pkgs "1Password CLI" {
+        default = [ "_1password" ];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    users.groups.onepassword-cli.gid = cfg.gid;
+
+    security.wrappers = {
+      "op" = {
+        source = "${cfg.package}/bin/op";
+        owner = "root";
+        group = "onepassword-cli";
+        setuid = false;
+        setgid = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/programs/adb.nix b/nixos/modules/programs/adb.nix
index 83bcfe886aa..9e9e37f92a8 100644
--- a/nixos/modules/programs/adb.nix
+++ b/nixos/modules/programs/adb.nix
@@ -23,8 +23,7 @@ with lib;
   ###### implementation
   config = mkIf config.programs.adb.enable {
     services.udev.packages = [ pkgs.android-udev-rules ];
-    # Give platform-tools lower priority so mke2fs+friends are taken from other packages first
-    environment.systemPackages = [ (lowPrio pkgs.androidenv.androidPkgs_9_0.platform-tools) ];
+    environment.systemPackages = [ pkgs.android-tools ];
     users.groups.adbusers = {};
   };
 }
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index 8a1653318ab..4b8bec33eb8 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -108,5 +108,8 @@ in
     # for google-chrome https://www.chromium.org/administrators/linux-quick-start
     environment.etc."opt/chrome/policies/managed/default.json".text = builtins.toJSON defaultProfile;
     environment.etc."opt/chrome/policies/managed/extra.json".text = builtins.toJSON cfg.extraOpts;
+    # for brave
+    environment.etc."brave/policies/managed/default.json".text = builtins.toJSON defaultProfile;
+    environment.etc."brave/policies/managed/extra.json".text = builtins.toJSON cfg.extraOpts;
   };
 }
diff --git a/nixos/modules/programs/mininet.nix b/nixos/modules/programs/mininet.nix
index 6e90e7669ac..2cf6c014c35 100644
--- a/nixos/modules/programs/mininet.nix
+++ b/nixos/modules/programs/mininet.nix
@@ -23,8 +23,8 @@ let
       ln -s ${pyEnv}/bin/mn $out/bin/mn
 
       # mn errors out without a telnet binary
-      # pkgs.telnet brings an undesired ifconfig into PATH see #43105
-      ln -s ${pkgs.telnet}/bin/telnet $out/bin/telnet
+      # pkgs.inetutils brings an undesired ifconfig into PATH see #43105
+      ln -s ${pkgs.inetutils}/bin/telnet $out/bin/telnet
     '';
 in
 {
diff --git a/nixos/modules/programs/nethoscope.nix b/nixos/modules/programs/nethoscope.nix
new file mode 100644
index 00000000000..495548e9c65
--- /dev/null
+++ b/nixos/modules/programs/nethoscope.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.nethoscope;
+in
+{
+  meta.maintainers = with maintainers; [ _0x4A6F ];
+
+  options = {
+    programs.nethoscope = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add nethoscope to the global environment and configure a
+          setcap wrapper for it.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ nethoscope ];
+    security.wrappers.nethoscope = {
+      source = "${pkgs.nethoscope}/bin/nethoscope";
+      capabilities = "cap_net_raw,cap_net_admin=eip";
+    };
+  };
+}
diff --git a/nixos/modules/programs/nix-ld.nix b/nixos/modules/programs/nix-ld.nix
new file mode 100644
index 00000000000..810a74ab50b
--- /dev/null
+++ b/nixos/modules/programs/nix-ld.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, config, ... }:
+{
+  meta.maintainers = [ lib.maintainers.mic92 ];
+  options = {
+    programs.nix-ld.enable = lib.mkEnableOption ''nix-ld, Documentation: <link xlink:href="https://github.com/Mic92/nix-ld"/>'';
+  };
+  config = lib.mkIf config.programs.nix-ld.enable {
+    systemd.tmpfiles.rules = [
+      "L+ ${pkgs.nix-ld.ldPath} - - - - ${pkgs.nix-ld}/libexec/nix-ld"
+    ];
+  };
+}
diff --git a/nixos/modules/programs/nncp.nix b/nixos/modules/programs/nncp.nix
new file mode 100644
index 00000000000..29a703eadf1
--- /dev/null
+++ b/nixos/modules/programs/nncp.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  nncpCfgFile = "/run/nncp.hjson";
+  programCfg = config.programs.nncp;
+  settingsFormat = pkgs.formats.json { };
+  jsonCfgFile = settingsFormat.generate "nncp.json" programCfg.settings;
+  pkg = programCfg.package;
+in {
+  options.programs.nncp = {
+
+    enable =
+      mkEnableOption "NNCP (Node to Node copy) utilities and configuration";
+
+    group = mkOption {
+      type = types.str;
+      default = "uucp";
+      description = ''
+        The group under which NNCP files shall be owned.
+        Any member of this group may access the secret keys
+        of this NNCP node.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.nncp;
+      defaultText = literalExpression "pkgs.nncp";
+      description = "The NNCP package to use system-wide.";
+    };
+
+    secrets = mkOption {
+      type = with types; listOf str;
+      example = [ "/run/keys/nncp.hjson" ];
+      description = ''
+        A list of paths to NNCP configuration files that should not be
+        in the Nix store. These files are layered on top of the values at
+        <xref linkend="opt-programs.nncp.settings"/>.
+      '';
+    };
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      description = ''
+        NNCP configuration, see
+        <link xlink:href="http://www.nncpgo.org/Configuration.html"/>.
+        At runtime these settings will be overlayed by the contents of
+        <xref linkend="opt-programs.nncp.secrets"/> into the file
+        <literal>${nncpCfgFile}</literal>. Node keypairs go in
+        <literal>secrets</literal>, do not specify them in
+        <literal>settings</literal> as they will be leaked into
+        <literal>/nix/store</literal>!
+      '';
+      default = { };
+    };
+
+  };
+
+  config = mkIf programCfg.enable {
+
+    environment = {
+      systemPackages = [ pkg ];
+      etc."nncp.hjson".source = nncpCfgFile;
+    };
+
+    programs.nncp.settings = {
+      spool = mkDefault "/var/spool/nncp";
+      log = mkDefault "/var/spool/nncp/log";
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${programCfg.settings.spool} 0770 root ${programCfg.group}"
+      "f ${programCfg.settings.log} 0770 root ${programCfg.group}"
+    ];
+
+    systemd.services.nncp-config = {
+      path = [ pkg ];
+      description = "Generate NNCP configuration";
+      wantedBy = [ "basic.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        umask u=rw
+        nncpCfgDir=$(mktemp --directory nncp.XXX)
+        for f in ${jsonCfgFile} ${toString config.programs.nncp.secrets}; do
+          tmpdir=$(mktemp --directory nncp.XXX)
+          nncp-cfgdir -cfg $f -dump $tmpdir
+          find $tmpdir -size 1c -delete
+          cp -a $tmpdir/* $nncpCfgDir/
+          rm -rf $tmpdir
+        done
+        nncp-cfgdir -load $nncpCfgDir > ${nncpCfgFile}
+        rm -rf $nncpCfgDir
+        chgrp ${programCfg.group} ${nncpCfgFile}
+        chmod g+r ${nncpCfgFile}
+      '';
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ ehmry ];
+}
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index b31fce91524..75685de4f04 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -157,9 +157,13 @@ in
               default = [ name ] ++ config.extraHostNames;
               defaultText = literalExpression "[ ${name} ] ++ config.${options.extraHostNames}";
               description = ''
-                DEPRECATED, please use <literal>extraHostNames</literal>.
                 A list of host names and/or IP numbers used for accessing
-                the host's ssh service.
+                the host's ssh service. This list includes the name of the
+                containing <literal>knownHosts</literal> attribute by default
+                for convenience. If you wish to configure multiple host keys
+                for the same host use multiple <literal>knownHosts</literal>
+                entries with different attribute names and the same
+                <literal>hostNames</literal> list.
               '';
             };
             extraHostNames = mkOption {
@@ -167,7 +171,8 @@ in
               default = [];
               description = ''
                 A list of additional host names and/or IP numbers used for
-                accessing the host's ssh service.
+                accessing the host's ssh service. This list is ignored if
+                <literal>hostNames</literal> is set explicitly.
               '';
             };
             publicKey = mkOption {
@@ -198,7 +203,12 @@ in
           };
         }));
         description = ''
-          The set of system-wide known SSH hosts.
+          The set of system-wide known SSH hosts. To make simple setups more
+          convenient the name of an attribute in this set is used as a host name
+          for the entry. This behaviour can be disabled by setting
+          <literal>hostNames</literal> explicitly. You can use
+          <literal>extraHostNames</literal> to add additional host names without
+          disabling this default.
         '';
         example = literalExpression ''
           {
@@ -207,6 +217,10 @@ in
               publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
             };
             "myhost2.net".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILIRuJ8p1Fi+m6WkHV0KWnRfpM1WxoW8XAS+XvsSKsTK";
+            "myhost2.net/dsa" = {
+              hostNames = [ "myhost2.net" ];
+              publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub;
+            };
           }
         '';
       };
@@ -279,9 +293,6 @@ in
         message = "knownHost ${name} must contain either a publicKey or publicKeyFile";
       });
 
-    warnings = mapAttrsToList (name: _: ''programs.ssh.knownHosts.${name}.hostNames is deprecated, use programs.ssh.knownHosts.${name}.extraHostNames'')
-      (filterAttrs (name: {hostNames, extraHostNames, ...}: hostNames != [ name ] ++ extraHostNames) cfg.knownHosts);
-
     # SSH configuration. Slight duplication of the sshd_config
     # generation in the sshd service.
     environment.etc."ssh/ssh_config".text =
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
deleted file mode 100644
index b454bf35229..00000000000
--- a/nixos/modules/programs/ssmtp.nix
+++ /dev/null
@@ -1,190 +0,0 @@
-# Configuration for `ssmtp', a trivial mail transfer agent that can
-# replace sendmail/postfix on simple systems.  It delivers email
-# directly to an SMTP server defined in its configuration file, without
-# queueing mail locally.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.ssmtp;
-
-in
-{
-
-  imports = [
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "directDelivery" ] [ "services" "ssmtp" "enable" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "hostName" ] [ "services" "ssmtp" "hostName" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "domain" ] [ "services" "ssmtp" "domain" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "root" ] [ "services" "ssmtp" "root" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "useTLS" ] [ "services" "ssmtp" "useTLS" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "useSTARTTLS" ] [ "services" "ssmtp" "useSTARTTLS" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "authUser" ] [ "services" "ssmtp" "authUser" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "authPassFile" ] [ "services" "ssmtp" "authPassFile" ])
-    (mkRenamedOptionModule [ "networking" "defaultMailServer" "setSendmail" ] [ "services" "ssmtp" "setSendmail" ])
-
-    (mkRemovedOptionModule [ "networking" "defaultMailServer" "authPass" ] "authPass has been removed since it leaks the clear-text password into the world-readable store. Use authPassFile instead and make sure it's not a store path")
-    (mkRemovedOptionModule [ "services" "ssmtp" "authPass" ] "authPass has been removed since it leaks the clear-text password into the world-readable store. Use authPassFile instead and make sure it's not a store path")
-  ];
-
-  options = {
-
-    services.ssmtp = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Use the trivial Mail Transfer Agent (MTA)
-          <command>ssmtp</command> package to allow programs to send
-          e-mail.  If you don't want to run a “real” MTA like
-          <command>sendmail</command> or <command>postfix</command> on
-          your machine, set this option to <literal>true</literal>, and
-          set the option
-          <option>services.ssmtp.hostName</option> to the
-          host name of your preferred mail server.
-        '';
-      };
-
-      settings = mkOption {
-        type = with types; attrsOf (oneOf [ bool str ]);
-        default = {};
-        description = ''
-          <citerefentry><refentrytitle>ssmtp</refentrytitle><manvolnum>5</manvolnum></citerefentry> configuration. Refer
-          to <link xlink:href="https://linux.die.net/man/5/ssmtp.conf"/> for details on supported values.
-        '';
-        example = literalExpression ''
-          {
-            Debug = true;
-            FromLineOverride = false;
-          }
-        '';
-      };
-
-      hostName = mkOption {
-        type = types.str;
-        example = "mail.example.org";
-        description = ''
-          The host name of the default mail server to use to deliver
-          e-mail. Can also contain a port number (ex: mail.example.org:587),
-          defaults to port 25 if no port is given.
-        '';
-      };
-
-      root = mkOption {
-        type = types.str;
-        default = "";
-        example = "root@example.org";
-        description = ''
-          The e-mail to which mail for users with UID &lt; 1000 is forwarded.
-        '';
-      };
-
-      domain = mkOption {
-        type = types.str;
-        default = "";
-        example = "example.org";
-        description = ''
-          The domain from which mail will appear to be sent.
-        '';
-      };
-
-      useTLS = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether TLS should be used to connect to the default mail
-          server.
-        '';
-      };
-
-      useSTARTTLS = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether the STARTTLS should be used to connect to the default
-          mail server.  (This is needed for TLS-capable mail servers
-          running on the default SMTP port 25.)
-        '';
-      };
-
-      authUser = mkOption {
-        type = types.str;
-        default = "";
-        example = "foo@example.org";
-        description = ''
-          Username used for SMTP auth. Leave blank to disable.
-        '';
-      };
-
-      authPassFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/run/keys/ssmtp-authpass";
-        description = ''
-          Path to a file that contains the password used for SMTP auth. The file
-          should not contain a trailing newline, if the password does not contain one
-          (e.g. use <command>echo -n "password" > file</command>).
-          This file should be readable by the users that need to execute ssmtp.
-        '';
-      };
-
-      setSendmail = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Whether to set the system sendmail to ssmtp's.";
-      };
-
-    };
-
-  };
-
-
-  config = mkIf cfg.enable {
-
-    assertions = [
-      {
-        assertion = cfg.useSTARTTLS -> cfg.useTLS;
-        message = "services.ssmtp.useSTARTTLS has no effect without services.ssmtp.useTLS";
-      }
-    ];
-
-    services.ssmtp.settings = mkMerge [
-      ({
-        MailHub = cfg.hostName;
-        FromLineOverride = mkDefault true;
-        UseTLS = cfg.useTLS;
-        UseSTARTTLS = cfg.useSTARTTLS;
-      })
-      (mkIf (cfg.root != "") { root = cfg.root; })
-      (mkIf (cfg.domain != "") { rewriteDomain = cfg.domain; })
-      (mkIf (cfg.authUser != "") { AuthUser = cfg.authUser; })
-      (mkIf (cfg.authPassFile != null) { AuthPassFile = cfg.authPassFile; })
-    ];
-
-    # careful here: ssmtp REQUIRES all config lines to end with a newline char!
-    environment.etc."ssmtp/ssmtp.conf".text = with generators; toKeyValue {
-      mkKeyValue = mkKeyValueDefault {
-        mkValueString = value:
-          if value == true then "YES"
-          else if value == false then "NO"
-          else mkValueStringDefault {} value
-        ;
-      } "=";
-    } cfg.settings;
-
-    environment.systemPackages = [pkgs.ssmtp];
-
-    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail {
-      program = "sendmail";
-      source = "${pkgs.ssmtp}/bin/sendmail";
-      setuid = false;
-      setgid = false;
-      owner = "root";
-      group = "root";
-    };
-
-  };
-
-}
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 195cf87e6a8..1d226276493 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -69,6 +69,11 @@ with lib;
     '')
     (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "seeks" ] "")
+    (mkRemovedOptionModule [ "services" "ssmtp" ] ''
+      The ssmtp package and the corresponding module have been removed due to
+      the program being unmaintained. The options `programs.msmtp.*` can be
+      used instead.
+    '')
     (mkRemovedOptionModule [ "services" "venus" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "wakeonlan"] "This module was removed in favor of enabling it with networking.interfaces.<name>.wakeOnLan")
     (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
@@ -91,6 +96,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "shellinabox" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "gogoclient" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index c0ef8b5f30b..530304b497a 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -61,6 +61,19 @@ let
         '';
       };
 
+      usshAuth = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If set, users with an SSH certificate containing an authorized principal
+          in their SSH agent are able to log in. Specific options are controlled
+          using the <option>security.pam.ussh</option> options.
+
+          Note that the  <option>security.pam.ussh.enable</option> must also be
+          set for this option to take effect.
+        '';
+      };
+
       yubicoAuth = mkOption {
         default = config.security.pam.yubico.enable;
         defaultText = literalExpression "config.security.pam.yubico.enable";
@@ -475,6 +488,9 @@ let
           optionalString cfg.usbAuth ''
             auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
           '' +
+          (let ussh = config.security.pam.ussh; in optionalString (config.security.pam.ussh.enable && cfg.usshAuth) ''
+            auth ${ussh.control} ${pkgs.pam_ussh}/lib/security/pam_ussh.so ${optionalString (ussh.caFile != null) "ca_file=${ussh.caFile}"} ${optionalString (ussh.authorizedPrincipals != null) "authorized_principals=${ussh.authorizedPrincipals}"} ${optionalString (ussh.authorizedPrincipalsFile != null) "authorized_principals_file=${ussh.authorizedPrincipalsFile}"} ${optionalString (ussh.group != null) "group=${ussh.group}"}
+          '') +
           (let oath = config.security.pam.oath; in optionalString cfg.oathAuth ''
             auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
           '') +
@@ -594,6 +610,7 @@ let
             session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
           '' +
           optionalString cfg.pamMount ''
+            session [success=1 default=ignore] ${pkgs.pam}/lib/security/pam_succeed_if.so service = systemd-user quiet
             session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
           '' +
           optionalString use_ldap ''
@@ -926,6 +943,96 @@ in
       };
     };
 
+    security.pam.ussh = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enables Uber's USSH PAM (<literal>pam-ussh</literal>) module.
+
+          This is similar to <literal>pam-ssh-agent</literal>, except that
+          the presence of a CA-signed SSH key with a valid principal is checked
+          instead.
+
+          Note that this module must both be enabled using this option and on a
+          per-PAM-service level as well (using <literal>usshAuth</literal>).
+
+          More information can be found <link
+          xlink:href="https://github.com/uber/pam-ussh">here</link>.
+        '';
+      };
+
+      caFile = mkOption {
+        default = null;
+        type = with types; nullOr path;
+        description = ''
+          By default <literal>pam-ussh</literal> reads the trusted user CA keys
+          from <filename>/etc/ssh/trusted_user_ca</filename>.
+
+          This should be set the same as your <literal>TrustedUserCAKeys</literal>
+          option for sshd.
+        '';
+      };
+
+      authorizedPrincipals = mkOption {
+        default = null;
+        type = with types; nullOr commas;
+        description = ''
+          Comma-separated list of authorized principals to permit; if the user
+          presents a certificate with one of these principals, then they will be
+          authorized.
+
+          Note that <literal>pam-ussh</literal> also requires that the certificate
+          contain a principal matching the user's username. The principals from
+          this list are in addition to those principals.
+
+          Mutually exclusive with <literal>authorizedPrincipalsFile</literal>.
+        '';
+      };
+
+      authorizedPrincipalsFile = mkOption {
+        default = null;
+        type = with types; nullOr path;
+        description = ''
+          Path to a list of principals; if the user presents a certificate with
+          one of these principals, then they will be authorized.
+
+          Note that <literal>pam-ussh</literal> also requires that the certificate
+          contain a principal matching the user's username. The principals from
+          this file are in addition to those principals.
+
+          Mutually exclusive with <literal>authorizedPrincipals</literal>.
+        '';
+      };
+
+      group = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        description = ''
+          If set, then the authenticating user must be a member of this group
+          to use this module.
+        '';
+      };
+
+      control = mkOption {
+        default = "sufficient";
+        type = types.enum [ "required" "requisite" "sufficient" "optional" ];
+        description = ''
+          This option sets pam "control".
+          If you want to have multi factor authentication, use "required".
+          If you want to use the SSH certificate instead of the regular password,
+          use "sufficient".
+
+          Read
+          <citerefentry>
+            <refentrytitle>pam.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for better understanding of this option.
+        '';
+      };
+    };
+
     security.pam.yubico = {
       enable = mkOption {
         default = false;
@@ -1110,6 +1217,9 @@ in
       optionalString (isEnabled (cfg: cfg.usbAuth)) ''
         mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
       '' +
+      optionalString (isEnabled (cfg: cfg.usshAuth)) ''
+        mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
+      '' +
       optionalString (isEnabled (cfg: cfg.oathAuth)) ''
         "mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,
       '' +
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index 462b7f89e2f..1d0efee8ca8 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -5,6 +5,14 @@ with lib;
 let
   cfg = config.security.pam.mount;
 
+  oflRequired = cfg.logoutHup || cfg.logoutTerm || cfg.logoutKill;
+
+  fake_ofl = pkgs.writeShellScriptBin "fake_ofl" ''
+    SIGNAL=$1
+    MNTPT=$2
+    ${pkgs.lsof}/bin/lsof | ${pkgs.gnugrep}/bin/grep $MNTPT | ${pkgs.gawk}/bin/awk '{print $2}' | ${pkgs.findutils}/bin/xargs ${pkgs.util-linux}/bin/kill -$SIGNAL
+  '';
+
   anyPamMount = any (attrByPath ["pamMount"] false) (attrValues config.security.pam.services);
 in
 
@@ -51,6 +59,71 @@ in
           You can define volume-specific options in the volume definitions.
         '';
       };
+
+      debugLevel = mkOption {
+        type = types.int;
+        default = 0;
+        example = 1;
+        description = ''
+          Sets the Debug-Level. 0 disables debugging, 1 enables pam_mount tracing,
+          and 2 additionally enables tracing in mount.crypt. The default is 0.
+          For more information, visit <link
+          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+        '';
+      };
+
+      logoutWait = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Amount of microseconds to wait until killing remaining processes after
+          final logout.
+          For more information, visit <link
+          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+        '';
+      };
+
+      logoutHup = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Kill remaining processes after logout by sending a SIGHUP.
+        '';
+      };
+
+      logoutTerm = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Kill remaining processes after logout by sending a SIGTERM.
+        '';
+      };
+
+      logoutKill = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Kill remaining processes after logout by sending a SIGKILL.
+        '';
+      };
+
+      createMountPoints = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Create mountpoints for volumes if they do not exist.
+        '';
+      };
+
+      removeCreatedMountPoints = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Remove mountpoints created by pam_mount after logout. This
+          only affects mountpoints that have been created by pam_mount
+          in the same session.
+        '';
+      };
     };
 
   };
@@ -77,21 +150,20 @@ in
           <!DOCTYPE pam_mount SYSTEM "pam_mount.conf.xml.dtd">
           <!-- auto generated from Nixos: modules/config/users-groups.nix -->
           <pam_mount>
-          <debug enable="0" />
-
+          <debug enable="${toString cfg.debugLevel}" />
           <!-- if activated, requires ofl from hxtools to be present -->
-          <logout wait="0" hup="no" term="no" kill="no" />
+          <logout wait="${toString cfg.logoutWait}" hup="${if cfg.logoutHup then "yes" else "no"}" term="${if cfg.logoutTerm then "yes" else "no"}" kill="${if cfg.logoutKill then "yes" else "no"}" />
           <!-- set PATH variable for pam_mount module -->
           <path>${makeBinPath ([ pkgs.util-linux ] ++ cfg.additionalSearchPaths)}</path>
           <!-- create mount point if not present -->
-          <mkmountpoint enable="1" remove="true" />
-
+          <mkmountpoint enable="${if cfg.createMountPoints then "1" else "0"}" remove="${if cfg.removeCreatedMountPoints then "true" else "false"}" />
           <!-- specify the binaries to be called -->
           <fusemount>${pkgs.fuse}/bin/mount.fuse %(VOLUME) %(MNTPT) -o ${concatStringsSep "," (cfg.fuseMountOptions ++ [ "%(OPTIONS)" ])}</fusemount>
+          <fuseumount>${pkgs.fuse}/bin/fusermount -u %(MNTPT)</fuseumount>
           <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount>
           <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount>
           <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun>
-
+          ${optionalString oflRequired "<ofl>${fake_ofl}/bin/fake_ofl %(SIGNAL) %(MNTPT)</ofl>"}
           ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
           ${concatStringsSep "\n" cfg.extraVolumes}
           </pam_mount>
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 99e578f8ada..4bf239fca8f 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -245,7 +245,7 @@ in
 
     environment.systemPackages = [ sudo ];
 
-    security.pam.services.sudo = { sshAgentAuth = true; };
+    security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
 
     environment.etc.sudoers =
       { source =
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index 6d5ce98df89..91d97a0b551 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 
@@ -101,6 +101,8 @@ in {
 
       openFirewall = mkOption {
         type = types.bool;
+        # Make the behavior consistent with other services. Set the default to
+        # false and remove the accompanying warning after NixOS 22.05 is released.
         default = true;
         description = ''
           Whether to automatically open the specified ports in the firewall.
@@ -273,10 +275,16 @@ in {
 
   config = mkIf cfg.enable {
 
-    # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
-    warnings = filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
-      services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
-    '' else "") cfg.streams);
+    warnings =
+      # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
+      filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
+        services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
+      '' else "") cfg.streams)
+      # Remove this warning after NixOS 22.05 is released.
+      ++ optional (options.services.snapserver.openFirewall.highestPrio >= (mkOptionDefault null).priority) ''
+        services.snapserver.openFirewall will no longer default to true starting with NixOS 22.11.
+        Enable it explicitly if you need to control Snapserver remotely.
+      '';
 
     systemd.services.snapserver = {
       after = [ "network.target" ];
@@ -304,8 +312,8 @@ in {
 
     networking.firewall.allowedTCPPorts =
       optionals cfg.openFirewall [ cfg.port ]
-      ++ optional cfg.tcp.enable cfg.tcp.port
-      ++ optional cfg.http.enable cfg.http.port;
+      ++ optional (cfg.openFirewall && cfg.tcp.enable) cfg.tcp.port
+      ++ optional (cfg.openFirewall && cfg.http.enable) cfg.http.port;
   };
 
   meta = {
diff --git a/nixos/modules/services/backup/zrepl.nix b/nixos/modules/services/backup/zrepl.nix
index 4356479b663..73f5e4d9f6d 100644
--- a/nixos/modules/services/backup/zrepl.nix
+++ b/nixos/modules/services/backup/zrepl.nix
@@ -38,6 +38,9 @@ in
     environment.etc."zrepl/zrepl.yml".source = configFile;
 
     systemd.packages = [ pkgs.zrepl ];
+
+    # Note that pkgs.zrepl copies and adapts the upstream systemd unit, and
+    # the fields defined here only override certain fields from that unit.
     systemd.services.zrepl = {
       requires = [ "local-fs.target" ];
       wantedBy = [ "zfs.target" ];
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index aaa159d3cb1..80c6c6abfd0 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -64,7 +64,7 @@ in {
         description = "Factory Steps";
         default = [];
         example = [
-          "steps.Git(repourl='git://github.com/buildbot/pyflakes.git', mode='incremental')"
+          "steps.Git(repourl='https://github.com/buildbot/pyflakes.git', mode='incremental')"
           "steps.ShellCommand(command=['trial', 'pyflakes'])"
         ];
       };
@@ -74,7 +74,7 @@ in {
         description = "List of Change Sources.";
         default = [];
         example = [
-          "changes.GitPoller('git://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+          "changes.GitPoller('https://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
         ];
       };
 
diff --git a/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix
index a7645e1f56e..30dd919b81a 100644
--- a/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixos/modules/services/continuous-integration/github-runner.nix
@@ -299,6 +299,16 @@ in
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         UMask = "0066";
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        SystemCallFilter = [
+          "~@debug"
+          "~@mount"
+          "~@privileged"
+          "~@cpu-emulation"
+          "~@obsolete"
+        ];
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
 
         # Needs network access
         PrivateNetwork = false;
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index dc58c634523..85ac0fb2a89 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -36,12 +36,12 @@ let
 
       # register new services
       ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
-        if echo "$NEW_SERVICES" | grep -xq ${name}; then
+        if echo "$NEW_SERVICES" | grep -xq "${name}"; then
           bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
             "set -a && source ${service.registrationConfigFile} &&"
             "gitlab-runner register"
             "--non-interactive"
-            "--name ${name}"
+            (if service.description != null then "--description \"${service.description}\"" else "--name '${name}'")
             "--executor ${service.executor}"
             "--limit ${toString service.limit}"
             "--request-concurrency ${toString service.requestConcurrency}"
@@ -365,6 +365,13 @@ in
               with <literal>RUNNER_ENV</literal> variable set.
             '';
           };
+          description = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = ''
+              Name/description of the runner.
+            '';
+          };
           executor = mkOption {
             type = types.str;
             default = "docker";
diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix
index 3c0e6f78e74..871b9914fb2 100644
--- a/nixos/modules/services/continuous-integration/jenkins/slave.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 with lib;
 let
   cfg = config.services.jenkinsSlave;
@@ -46,6 +46,15 @@ in {
           this is the home of the "jenkins" user.
         '';
       };
+
+      javaPackage = mkOption {
+        default = pkgs.jdk;
+        defaultText = literalExpression "pkgs.jdk";
+        description = ''
+          Java package to install.
+        '';
+        type = types.package;
+      };
     };
   };
 
@@ -64,5 +73,10 @@ in {
         uid = config.ids.uids.jenkins;
       };
     };
+
+    programs.java = {
+      enable = true;
+      package = cfg.javaPackage;
+    };
   };
 }
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index eb061af9262..9a7aebe4f6a 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -6,46 +6,44 @@ let
   cfg = config.services.cockroachdb;
   crdb = cfg.package;
 
-  escape    = builtins.replaceStrings ["%"] ["%%"];
-  ifNotNull = v: s: optionalString (v != null) s;
-
-  startupCommand = lib.concatStringsSep " "
-    [ # Basic startup
-      "${crdb}/bin/cockroach start"
+  startupCommand = utils.escapeSystemdExecArgs
+    ([
+      # Basic startup
+      "${crdb}/bin/cockroach"
+      "start"
       "--logtostderr"
       "--store=/var/lib/cockroachdb"
-      (ifNotNull cfg.locality "--locality='${cfg.locality}'")
 
       # WebUI settings
-      "--http-addr='${cfg.http.address}:${toString cfg.http.port}'"
+      "--http-addr=${cfg.http.address}:${toString cfg.http.port}"
 
       # Cluster listen address
-      "--listen-addr='${cfg.listen.address}:${toString cfg.listen.port}'"
-
-      # Cluster configuration
-      (ifNotNull cfg.join "--join=${cfg.join}")
+      "--listen-addr=${cfg.listen.address}:${toString cfg.listen.port}"
 
-      # Cache and memory settings. Must be escaped.
-      "--cache='${escape cfg.cache}'"
-      "--max-sql-memory='${escape cfg.maxSqlMemory}'"
+      # Cache and memory settings.
+      "--cache=${cfg.cache}"
+      "--max-sql-memory=${cfg.maxSqlMemory}"
 
       # Certificate/security settings.
       (if cfg.insecure then "--insecure" else "--certs-dir=${cfg.certsDir}")
-    ];
-
-    addressOption = descr: defaultPort: {
-      address = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "Address to bind to for ${descr}";
-      };
+    ]
+    ++ lib.optional (cfg.join != null) "--join=${cfg.join}"
+    ++ lib.optional (cfg.locality != null) "--locality=${cfg.locality}"
+    ++ cfg.extraArgs);
+
+  addressOption = descr: defaultPort: {
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "Address to bind to for ${descr}";
+    };
 
-      port = mkOption {
-        type = types.port;
-        default = defaultPort;
-        description = "Port to bind to for ${descr}";
-      };
+    port = mkOption {
+      type = types.port;
+      default = defaultPort;
+      description = "Port to bind to for ${descr}";
     };
+  };
 in
 
 {
@@ -159,6 +157,16 @@ in
           only contain open source features and open source code).
         '';
       };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--advertise-addr" "[fe80::f6f2:::]" ];
+        description = ''
+          Extra CLI arguments passed to <command>cockroach start</command>.
+          For the full list of supported argumemnts, check <link xlink:href="https://www.cockroachlabs.com/docs/stable/cockroach-start.html#flags"/>
+        '';
+      };
     };
   };
 
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index df0f62556df..b19fb33ec17 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
@@ -29,14 +29,7 @@
     },
     {
       "name": "libpipewire-module-protocol-pulse",
-      "args": {
-        "server.address": [
-          "unix:native"
-        ],
-        "vm.overrides": {
-          "pulse.min.quantum": "1024/48000"
-        }
-      }
+      "args": {}
     }
   ],
   "context.exec": [
@@ -46,6 +39,14 @@
     }
   ],
   "stream.properties": {},
+  "pulse.properties": {
+    "server.address": [
+      "unix:native"
+    ],
+    "vm.overrides": {
+      "pulse.min.quantum": "1024/48000"
+    }
+  },
   "pulse.rules": [
     {
       "matches": [
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
index 109c91134b9..09761d6300e 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -58,7 +58,7 @@ in {
             Configuration for the media session core. For details see
             https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
           '';
-          default = {};
+          default = defaults.media-session;
         };
 
         alsa-monitor = mkOption {
@@ -67,7 +67,7 @@ in {
             Configuration for the alsa monitor. For details see
             https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
           '';
-          default = {};
+          default = defaults.alsa-monitor;
         };
 
         bluez-monitor = mkOption {
@@ -76,7 +76,7 @@ in {
             Configuration for the bluez5 monitor. For details see
             https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
           '';
-          default = {};
+          default = defaults.bluez-monitor;
         };
 
         v4l2-monitor = mkOption {
@@ -85,7 +85,7 @@ in {
             Configuration for the V4L2 monitor. For details see
             https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
           '';
-          default = {};
+          default = defaults.v4l2-monitor;
         };
       };
     };
@@ -110,6 +110,11 @@ in {
       source = json.generate "v4l2-monitor.conf" configs.v4l2-monitor;
     };
 
+    environment.etc."pipewire/media-session.d/with-audio" =
+      mkIf config.services.pipewire.audio.enable {
+        text = "";
+      };
+
     environment.etc."pipewire/media-session.d/with-alsa" =
       mkIf config.services.pipewire.alsa.enable {
         text = "";
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index 59e9342a6ea..1323336d866 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -116,6 +116,16 @@ in {
         };
       };
 
+      audio = {
+        enable = lib.mkOption {
+          type = lib.types.bool;
+          # this is for backwards compatibility
+          default = cfg.alsa.enable || cfg.jack.enable || cfg.pulse.enable;
+          defaultText = lib.literalExpression "config.services.pipewire.alsa.enable || config.services.pipewire.jack.enable || config.services.pipewire.pulse.enable";
+          description = "Whether to use PipeWire as the primary sound server";
+        };
+      };
+
       alsa = {
         enable = mkEnableOption "ALSA support";
         support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems";
@@ -152,13 +162,18 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.pulse.enable -> !config.hardware.pulseaudio.enable;
-        message = "PipeWire based PulseAudio server emulation replaces PulseAudio. This option requires `hardware.pulseaudio.enable` to be set to false";
+        assertion = cfg.audio.enable -> !config.hardware.pulseaudio.enable;
+        message = "Using PipeWire as the sound server conflicts with PulseAudio. This option requires `hardware.pulseaudio.enable` to be set to false";
       }
       {
         assertion = cfg.jack.enable -> !config.services.jack.jackd.enable;
         message = "PipeWire based JACK emulation doesn't use the JACK service. This option requires `services.jack.jackd.enable` to be set to false";
       }
+      {
+        # JACK intentionally not checked, as PW-on-JACK setups are a thing that some people may want
+        assertion = (cfg.alsa.enable || cfg.pulse.enable) -> cfg.audio.enable;
+        message = "Using PipeWire's ALSA/PulseAudio compatibility layers requires running PipeWire as the sound server. Set `services.pipewire.audio.enable` to true.";
+      }
     ];
 
     environment.systemPackages = [ cfg.package ]
diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 52ec17b95db..1dbdd842c4a 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -1,7 +1,9 @@
 { config, lib, pkgs, ... }:
 
 let
-  cfg = config.services.pipewire.wireplumber;
+  pwCfg = config.services.pipewire;
+  cfg = pwCfg.wireplumber;
+  pwUsedForAudio = pwCfg.audio.enable;
 in
 {
   meta.maintainers = [ lib.maintainers.k900 ];
@@ -33,6 +35,14 @@ in
     ];
 
     environment.systemPackages = [ cfg.package ];
+
+    environment.etc."wireplumber/main.lua.d/80-nixos.lua" = lib.mkIf (!pwUsedForAudio) {
+     text = ''
+        -- Pipewire is not used for audio, so prevent it from grabbing audio devices
+        alsa_monitor.enable = function() end
+      '';
+    };
+
     systemd.packages = [ cfg.package ];
 
     systemd.services.wireplumber.enable = config.services.pipewire.systemWide;
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index bebb3c3f13f..4eacc4782a9 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -194,6 +194,7 @@ in {
         extraGroups = [ cfg.group ];
         home = "/var/lib/jupyter";
         createHome = true;
+        isSystemUser = true;
         useDefaultShell = true; # needed so that the user can start a terminal.
       };
     })
diff --git a/nixos/modules/services/editors/haste.nix b/nixos/modules/services/editors/haste.nix
new file mode 100644
index 00000000000..35fe26766ef
--- /dev/null
+++ b/nixos/modules/services/editors/haste.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  pkg = pkgs.haste-server;
+  cfg = config.services.haste-server;
+
+  format = pkgs.formats.json {};
+in
+{
+  options.services.haste-server = {
+    enable = mkEnableOption "haste-server";
+    openFirewall = mkEnableOption "firewall passthrough for haste-server";
+
+    settings = mkOption {
+      description = ''
+        Configuration for haste-server.
+        For documentation see <link xlink:href="https://github.com/toptal/haste-server#settings">project readme</link>
+      '';
+      type = format.type;
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.settings.port ];
+
+    services.haste-server = {
+      settings = {
+        host = mkDefault "::";
+        port = mkDefault 7777;
+
+        keyLength = mkDefault 10;
+        maxLength = mkDefault 400000;
+
+        staticMaxAge = mkDefault 86400;
+        recompressStaticAssets = mkDefault false;
+
+        logging = mkDefault [
+          {
+            level = "verbose";
+            type = "Console";
+            colorize = true;
+          }
+        ];
+
+        keyGenerator = mkDefault {
+          type = "phonetic";
+        };
+
+        rateLimits = {
+          categories = {
+            normal = {
+              totalRequests = mkDefault 500;
+              every = mkDefault 60000;
+            };
+          };
+        };
+
+        storage = mkDefault {
+          type = "file";
+        };
+
+        documents = {
+          about = mkDefault "${pkg}/share/haste-server/about.md";
+        };
+      };
+    };
+
+    systemd.services.haste-server = {
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = "haste-server";
+        DynamicUser = true;
+        StateDirectory = "haste-server";
+        WorkingDirectory = "/var/lib/haste-server";
+        ExecStart = "${pkg}/bin/haste-server ${format.generate "config.json" cfg.settings}";
+      };
+
+      path = with pkgs; [ pkg coreutils ];
+    };
+  };
+}
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index 96fcd6d2c8b..ff73d7a46ed 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -53,6 +53,14 @@ in
         '';
       };
 
+      bind = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          The address to which the service should bind.
+        '';
+      };
+
       admins = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -241,6 +249,7 @@ in
           "${cfg.package}/bin/factorio"
           "--config=${cfg.configFile}"
           "--port=${toString cfg.port}"
+          "--bind=${cfg.bind}"
           "--start-server=${mkSavePath cfg.saveName}"
           "--server-settings=${serverSettingsFile}"
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index 5bb8eff5762..8233962c1a2 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -153,7 +153,7 @@ in {
         type = types.separatedString " ";
         default = "-Xmx2048M -Xms2048M";
         # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
-        example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+        example = "-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
           + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
           + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
         description = "JVM options for the Minecraft server.";
diff --git a/nixos/modules/services/hardware/joycond.nix b/nixos/modules/services/hardware/joycond.nix
index ffef4f8a4e1..d81c1bb6d63 100644
--- a/nixos/modules/services/hardware/joycond.nix
+++ b/nixos/modules/services/hardware/joycond.nix
@@ -22,13 +22,9 @@ with lib;
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [
-      kernelPackages.hid-nintendo
-      cfg.package
-    ];
+    environment.systemPackages = [ cfg.package ];
 
-    boot.extraModulePackages = [ kernelPackages.hid-nintendo ];
-    boot.kernelModules = [ "hid_nintendo" ];
+    boot.extraModulePackages = optional (versionOlder kernelPackages.kernel.version "5.16") kernelPackages.hid-nintendo;
 
     services.udev.packages = [ cfg.package ];
 
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 61448af2d33..8257eeb673b 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -23,17 +23,16 @@ let
   nixosRules = ''
     # Miscellaneous devices.
     KERNEL=="kvm",                  MODE="0666"
-    KERNEL=="kqemu",                MODE="0666"
 
     # Needed for gpm.
     SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
   '';
 
   # Perform substitutions in all udev rules files.
-  udevRules = pkgs.runCommand "udev-rules"
+  udevRulesFor = { name, udevPackages, udevPath, udev, systemd, binPackages, initrdBin ? null }: pkgs.runCommand name
     { preferLocalBuild = true;
       allowSubstitutes = false;
-      packages = unique (map toString cfg.packages);
+      packages = unique (map toString udevPackages);
     }
     ''
       mkdir -p $out
@@ -61,6 +60,9 @@ let
           --replace \"/bin/mount \"${pkgs.util-linux}/bin/mount \
           --replace /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
           --replace /usr/bin/basename ${pkgs.coreutils}/bin/basename
+      ${optionalString (initrdBin != null) ''
+        substituteInPlace $i --replace '/run/current-system/systemd' "${removeSuffix "/bin" initrdBin}"
+      ''}
       done
 
       echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
@@ -85,8 +87,9 @@ let
       for i in $import_progs $run_progs; do
         # if the path refers to /run/current-system/systemd, replace with config.systemd.package
         if [[ $i == /run/current-system/systemd* ]]; then
-          i="${config.systemd.package}/''${i#/run/current-system/systemd/}"
+          i="${systemd}/''${i#/run/current-system/systemd/}"
         fi
+
         if [[ ! -x $i ]]; then
           echo "FAIL"
           echo "$i is called in udev rules but is not executable or does not exist"
@@ -103,7 +106,7 @@ let
         echo "Consider fixing the following udev rules:"
         echo "$filesToFixup" | while read localFile; do
           remoteFile="origin unknown"
-          for i in ${toString cfg.packages}; do
+          for i in ${toString binPackages}; do
             for j in "$i"/*/udev/rules.d/*; do
               [ -e "$out/$(basename "$j")" ] || continue
               [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
@@ -126,7 +129,7 @@ let
       ${optionalString (!config.boot.hardwareScan) ''
         ln -s /dev/null $out/80-drivers.rules
       ''}
-    ''; # */
+    '';
 
   hwdbBin = pkgs.runCommand "hwdb.bin"
     { preferLocalBuild = true;
@@ -202,20 +205,6 @@ in
         '';
       };
 
-      initrdRules = mkOption {
-        default = "";
-        example = ''
-          SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
-        '';
-        type = types.lines;
-        description = ''
-          <command>udev</command> rules to include in the initrd
-          <emphasis>only</emphasis>. They'll be written into file
-          <filename>99-local.rules</filename>. Thus they are read and applied
-          after the essential initrd rules.
-        '';
-      };
-
       extraRules = mkOption {
         default = "";
         example = ''
@@ -283,6 +272,52 @@ in
       '';
     };
 
+    boot.initrd.services.udev = {
+
+      packages = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        visible = false;
+        description = ''
+          <emphasis>This will only be used when systemd is used in stage 1.</emphasis>
+
+          List of packages containing <command>udev</command> rules that will be copied to stage 1.
+          All files found in
+          <filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
+          <filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
+          will be included.
+        '';
+      };
+
+      binPackages = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        visible = false;
+        description = ''
+          <emphasis>This will only be used when systemd is used in stage 1.</emphasis>
+
+          Packages to search for binaries that are referenced by the udev rules in stage 1.
+          This list always contains /bin of the initrd.
+        '';
+        apply = map getBin;
+      };
+
+      rules = mkOption {
+        default = "";
+        example = ''
+          SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
+        '';
+        type = types.lines;
+        description = ''
+          <command>udev</command> rules to include in the initrd
+          <emphasis>only</emphasis>. They'll be written into file
+          <filename>99-local.rules</filename>. Thus they are read and applied
+          after the essential initrd rules.
+        '';
+      };
+
+    };
+
   };
 
 
@@ -298,16 +333,54 @@ in
 
     boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
 
-    boot.initrd.extraUdevRulesCommands = optionalString (cfg.initrdRules != "")
+    boot.initrd.extraUdevRulesCommands = optionalString (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
       ''
         cat <<'EOF' > $out/99-local.rules
-        ${cfg.initrdRules}
+        ${config.boot.initrd.services.udev.rules}
         EOF
       '';
 
+    boot.initrd.systemd.additionalUpstreamUnits = [
+      # TODO: "initrd-udevadm-cleanup-db.service" is commented out because of https://github.com/systemd/systemd/issues/12953
+      "systemd-udevd-control.socket"
+      "systemd-udevd-kernel.socket"
+      "systemd-udevd.service"
+      "systemd-udev-settle.service"
+      "systemd-udev-trigger.service"
+    ];
+    boot.initrd.systemd.storePaths = [
+      "${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd"
+      "${config.boot.initrd.systemd.package}/lib/udev"
+    ] ++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages;
+
+    # Generate the udev rules for the initrd
+    boot.initrd.systemd.contents = {
+      "/etc/udev/rules.d".source = udevRulesFor {
+        name = "initrd-udev-rules";
+        initrdBin = config.boot.initrd.systemd.contents."/bin".source;
+        udevPackages = config.boot.initrd.services.udev.packages;
+        udevPath = config.boot.initrd.systemd.contents."/bin".source;
+        udev = config.boot.initrd.systemd.package;
+        systemd = config.boot.initrd.systemd.package;
+        binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ];
+      };
+    };
+    # Insert custom rules
+    boot.initrd.services.udev.packages = mkIf (config.boot.initrd.services.udev.rules != "") (pkgs.writeTextFile {
+      name = "initrd-udev-rules";
+      destination = "/etc/udev/rules.d/99-local.rules";
+      text = config.boot.initrd.services.udev.rules;
+    });
+
     environment.etc =
       {
-        "udev/rules.d".source = udevRules;
+        "udev/rules.d".source = udevRulesFor {
+          name = "udev-rules";
+          udevPackages = cfg.packages;
+          systemd = config.systemd.package;
+          binPackages = cfg.packages;
+          inherit udevPath udev;
+        };
         "udev/hwdb.bin".source = hwdbBin;
       };
 
@@ -338,4 +411,8 @@ in
       };
 
   };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "udev" "initrdRules" ] [ "boot" "initrd" "services" "udev" "rules" ])
+  ];
 }
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
index 6be23f39754..ea552ce867e 100644
--- a/nixos/modules/services/hardware/udisks2.nix
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -4,6 +4,13 @@
 
 with lib;
 
+let
+  settingsFormat = pkgs.formats.ini {
+    listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
+  };
+  configFiles = mapAttrs (name: value: (settingsFormat.generate name value)) (mapAttrs' (name: value: nameValuePair name value ) config.services.udisks2.settings);
+in
+
 {
 
   ###### interface
@@ -21,6 +28,36 @@ with lib;
         '';
       };
 
+      settings = mkOption rec {
+        type = types.attrsOf settingsFormat.type;
+        apply = recursiveUpdate default;
+        default = {
+          "udisks2.conf" = {
+            udisks2 = {
+              modules = [ "*" ];
+              modules_load_preference = "ondemand";
+            };
+            defaults = {
+              encryption = "luks2";
+            };
+          };
+        };
+        example = literalExpression ''
+        {
+          "WDC-WD10EZEX-60M2NA0-WD-WCC3F3SJ0698.conf" = {
+            ATA = {
+              StandbyTimeout = 50;
+            };
+          };
+        };
+        '';
+        description = ''
+          Options passed to udisksd.
+          See <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html">here</link> and
+          drive configuration in <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html">here</link> for supported options.
+        '';
+      };
+
     };
 
   };
@@ -32,6 +69,8 @@ with lib;
 
     environment.systemPackages = [ pkgs.udisks2 ];
 
+    environment.etc = mapAttrs' (name: value: nameValuePair "udisks2/${name}" { source = value; } ) configFiles;
+
     security.polkit.enable = true;
 
     services.dbus.packages = [ pkgs.udisks2 ];
diff --git a/nixos/modules/services/hardware/usbrelayd.nix b/nixos/modules/services/hardware/usbrelayd.nix
new file mode 100644
index 00000000000..c0322e89e6b
--- /dev/null
+++ b/nixos/modules/services/hardware/usbrelayd.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.usbrelayd;
+in
+{
+  options.services.usbrelayd = with types; {
+    enable = mkEnableOption "USB Relay MQTT daemon";
+
+    broker = mkOption {
+      type = str;
+      description = "Hostname or IP address of your MQTT Broker.";
+      default = "127.0.0.1";
+      example = [
+        "mqtt"
+        "192.168.1.1"
+      ];
+    };
+
+    clientName = mkOption {
+      type = str;
+      description = "Name, your client connects as.";
+      default = "MyUSBRelay";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    # TODO: Rename to .conf in upcomming release
+    environment.etc."usbrelayd.ini".text = ''
+      [MQTT]
+      BROKER = ${cfg.broker}
+      CLIENTNAME = ${cfg.clientName}
+    '';
+
+    services.udev.packages = [ pkgs.usbrelayd ];
+    systemd.packages = [ pkgs.usbrelayd ];
+    users.users.usbrelay = {
+      isSystemUser = true;
+      group = "usbrelay";
+    };
+    users.groups.usbrelay = { };
+  };
+}
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index e6a23233ba2..28e2d18bf03 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -132,7 +132,7 @@ in
         description = "Graylog server daemon user";
       };
     };
-    users.groups = mkIf (cfg.user == "graylog") {};
+    users.groups = mkIf (cfg.user == "graylog") { graylog = {}; };
 
     systemd.tmpfiles.rules = [
       "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
diff --git a/nixos/modules/services/logging/klogd.nix b/nixos/modules/services/logging/klogd.nix
index 8d371c161eb..1de0e58abbb 100644
--- a/nixos/modules/services/logging/klogd.nix
+++ b/nixos/modules/services/logging/klogd.nix
@@ -1,38 +1,9 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
+{ lib, ... }:
 
 {
-  ###### interface
-
-  options = {
-
-    services.klogd.enable = mkOption {
-      type = types.bool;
-      default = versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5";
-      defaultText = literalExpression ''versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5"'';
-      description = ''
-        Whether to enable klogd, the kernel log message processing
-        daemon.  Since systemd handles logging of kernel messages on
-        Linux 3.5 and later, this is only useful if you're running an
-        older kernel.
-      '';
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.klogd.enable {
-    systemd.services.klogd = {
-      description = "Kernel Log Daemon";
-      wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.sysklogd ];
-      unitConfig.ConditionVirtualization = "!systemd-nspawn";
-      script =
-        "klogd -c 1 -2 -n " +
-        "-k $(dirname $(readlink -f /run/booted-system/kernel))/System.map";
-    };
-  };
+  imports = [
+    (lib.mkRemovedOptionModule [ "security" "klogd" "enable" ] ''
+      Logging of kernel messages is now handled by systemd.
+    '')
+  ];
 }
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 082cf92ff4e..e6eb0552c9e 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -5,7 +5,10 @@ with lib;
 let
   cfg = config.services.logrotate;
 
-  pathOpts = { name, ... }:  {
+  # deprecated legacy compat settings
+  # these options will be removed before 22.11 in the following PR:
+  # https://github.com/NixOS/nixpkgs/pull/164169
+  pathOpts = { name, ... }: {
     options = {
       enable = mkOption {
         type = types.bool;
@@ -86,23 +89,113 @@ let
     config.name = name;
   };
 
-  mkConf = pathOpts: ''
-    # generated by NixOS using the `services.logrotate.paths.${pathOpts.name}` attribute set
-    ${concatMapStringsSep " " (path: ''"${path}"'') (toList pathOpts.path)} {
-      ${optionalString (pathOpts.user != null || pathOpts.group != null) "su ${pathOpts.user} ${pathOpts.group}"}
-      ${pathOpts.frequency}
-      rotate ${toString pathOpts.keep}
-      ${pathOpts.extraConfig}
-    }
-  '';
-
-  paths = sortProperties (attrValues (filterAttrs (_: pathOpts: pathOpts.enable) cfg.paths));
-  configFile = pkgs.writeText "logrotate.conf" (
-    concatStringsSep "\n" (
-      [ "missingok" "notifempty" cfg.extraConfig ] ++ (map mkConf paths)
-    )
+  generateLine = n: v:
+    if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
+    else if builtins.elem n [ "extraConfig" "frequency" ] then "${v}\n"
+    else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
+         then "${n}\n    ${v}\n  endscript\n"
+    else if isInt v then "${n} ${toString v}\n"
+    else if v == true then "${n}\n"
+    else if v == false then "no${n}\n"
+    else "${n} ${v}\n";
+  generateSection = indent: settings: concatStringsSep (fixedWidthString indent " " "") (
+    filter (x: x != null) (mapAttrsToList generateLine settings)
   );
 
+  # generateSection includes a final newline hence weird closing brace
+  mkConf = settings:
+    if settings.global or false then generateSection 0 settings
+    else ''
+      ${concatMapStringsSep "\n" (files: ''"${files}"'') (toList settings.files)} {
+        ${generateSection 2 settings}}
+    '';
+
+  # below two mapPaths are compat functions
+  mapPathOptToSetting = n: v:
+    if n == "keep" then nameValuePair "rotate" v
+    else if n == "path" then nameValuePair "files" v
+    else nameValuePair n v;
+
+  mapPathsToSettings = path: pathOpts:
+    nameValuePair path (
+      filterAttrs (n: v: ! builtins.elem n [ "user" "group" "name" ] && v != "") (
+        (mapAttrs' mapPathOptToSetting pathOpts) //
+        {
+          su =
+            if pathOpts.user != null
+            then "${pathOpts.user} ${pathOpts.group}"
+            else null;
+        }
+      )
+    );
+
+  settings = sortProperties (attrValues (filterAttrs (_: settings: settings.enable) (
+    foldAttrs recursiveUpdate { } [
+      {
+        header = {
+          enable = true;
+          missingok = true;
+          notifempty = true;
+          frequency = "weekly";
+          rotate = 4;
+        };
+        # compat section
+        extraConfig = {
+          enable = (cfg.extraConfig != "");
+          global = true;
+          extraConfig = cfg.extraConfig;
+          priority = 101;
+        };
+      }
+      (mapAttrs' mapPathsToSettings cfg.paths)
+      cfg.settings
+      { header = { global = true; priority = 100; }; }
+    ]
+  )));
+  configFile = pkgs.writeTextFile {
+    name = "logrotate.conf";
+    text = concatStringsSep "\n" (
+      map mkConf settings
+    );
+    checkPhase = optionalString cfg.checkConfig ''
+      # logrotate --debug also checks that users specified in config
+      # file exist, but we only have sandboxed users here so brown these
+      # out. according to man page that means su, create and createolddir.
+      # files required to exist also won't be present, so missingok is forced.
+      user=$(${pkgs.buildPackages.coreutils}/bin/id -un)
+      group=$(${pkgs.buildPackages.coreutils}/bin/id -gn)
+      sed -e "s/\bsu\s.*/su $user $group/" \
+          -e "s/\b\(create\s\+[0-9]*\s*\|createolddir\s\+[0-9]*\s\+\).*/\1$user $group/" \
+          -e "1imissingok" -e "s/\bnomissingok\b//" \
+          $out > /tmp/logrotate.conf
+      # Since this makes for very verbose builds only show real error.
+      # There is no way to control log level, but logrotate hardcodes
+      # 'error:' at common log level, so we can use grep, taking care
+      # to keep error codes
+      set -o pipefail
+      if ! ${pkgs.buildPackages.logrotate}/sbin/logrotate --debug /tmp/logrotate.conf 2>&1 \
+          | ( ! grep "error:" ) > /tmp/logrotate-error; then
+              echo "Logrotate configuration check failed."
+              echo "The failing configuration (after adjustments to pass tests in sandbox) was:"
+              printf "%s\n" "-------"
+              cat /tmp/logrotate.conf
+              printf "%s\n" "-------"
+              echo "The error reported by logrotate was as follow:"
+              printf "%s\n" "-------"
+              cat /tmp/logrotate-error
+              printf "%s\n" "-------"
+              echo "You can disable this check with services.logrotate.checkConfig = false,"
+              echo "but if you think it should work please report this failure along with"
+              echo "the config file being tested!"
+              false
+      fi
+    '';
+  };
+
+  mailOption =
+    if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
+    then "--mail=${pkgs.mailutils}/bin/mail"
+    else "";
 in
 {
   imports = [
@@ -112,17 +205,121 @@ in
   options = {
     services.logrotate = {
       enable = mkEnableOption "the logrotate systemd service" // {
-        default = foldr (n: a: a || n.enable) false (attrValues cfg.paths);
-        defaultText = literalExpression "cfg.paths != {}";
+        default = foldr (n: a: a || n.enable) false (attrValues cfg.settings);
+        defaultText = literalExpression "cfg.settings != {}";
+      };
+
+      settings = mkOption {
+        default = { };
+        description = ''
+          logrotate freeform settings: each attribute here will define its own section,
+          ordered by priority, which can either define files to rotate with their settings
+          or settings common to all further files settings.
+          Refer to <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+        '';
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ]));
+
+          options = {
+            enable = mkEnableOption "setting individual kill switch" // {
+              default = true;
+            };
+
+            global = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether this setting is a global option or not: set to have these
+                settings apply to all files settings with a higher priority.
+              '';
+            };
+            files = mkOption {
+              type = with types; either str (listOf str);
+              default = name;
+              defaultText = ''
+                The attrset name if not specified
+              '';
+              description = ''
+                Single or list of files for which rules are defined.
+                The files are quoted with double-quotes in logrotate configuration,
+                so globs and spaces are supported.
+                Note this setting is ignored if globals is true.
+              '';
+            };
+
+            frequency = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                How often to rotate the logs. Defaults to previously set global setting,
+                which itself defauts to weekly.
+              '';
+            };
+
+            priority = mkOption {
+              type = types.int;
+              default = 1000;
+              description = ''
+                Order of this logrotate block in relation to the others. The semantics are
+                the same as with `lib.mkOrder`. Smaller values are inserted first.
+              '';
+            };
+          };
+
+        }));
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = configFile;
+        defaultText = ''
+          A configuration file automatically generated by NixOS.
+        '';
+        description = ''
+          Override the configuration file used by MySQL. By default,
+          NixOS generates one automatically from <xref linkend="opt-services.logrotate.settings"/>.
+        '';
+        example = literalExpression ''
+          pkgs.writeText "logrotate.conf" '''
+            missingok
+            "/var/log/*.log" {
+              rotate 4
+              weekly
+            }
+          ''';
+        '';
       };
 
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether the config should be checked at build time.
+
+          Some options are not checkable at build time because of the build sandbox:
+          for example, the test does not know about existing files and system users are
+          not known.
+          These limitations mean we must adjust the file for tests (missingok is forced
+          and users are replaced by dummy users), so tests are complemented by a
+          logrotate-checkconf service that is enabled by default.
+          This extra check can be disabled by disabling it at the systemd level with the
+          <option>services.systemd.services.logrotate-checkconf.enable</option> option.
+
+          Conversely there are still things that might make this check fail incorrectly
+          (e.g. a file path where we don't have access to intermediate directories):
+          in this case you can disable the failing check with this option.
+        '';
+      };
+
+      # deprecated legacy compat settings
       paths = mkOption {
         type = with types; attrsOf (submodule pathOpts);
-        default = {};
+        default = { };
         description = ''
           Attribute set of paths to rotate. The order each block appears in the generated configuration file
           can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
           using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
+          This setting has been deprecated in favor of <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
         example = literalExpression ''
           {
@@ -151,19 +348,37 @@ in
         description = ''
           Extra contents to append to the logrotate configuration file. Refer to
           <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+          This setting has been deprecated in favor of
+          <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    assertions = mapAttrsToList (name: pathOpts:
-      { assertion = (pathOpts.user != null) == (pathOpts.group != null);
-        message = ''
-          If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
-        '';
-      }
-    ) cfg.paths;
+    assertions =
+      mapAttrsToList
+        (name: pathOpts:
+          {
+            assertion = (pathOpts.user != null) == (pathOpts.group != null);
+            message = ''
+              If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
+            '';
+          })
+        cfg.paths;
+
+    warnings =
+      (mapAttrsToList
+        (name: pathOpts: ''
+          Using config.services.logrotate.paths.${name} is deprecated and will become unsupported in a future release.
+          Please use services.logrotate.settings instead.
+        '')
+        cfg.paths
+      ) ++
+      (optional (cfg.extraConfig != "") ''
+        Using config.services.logrotate.extraConfig is deprecated and will become unsupported in a future release.
+        Please use services.logrotate.settings with globals=true instead.
+      '');
 
     systemd.services.logrotate = {
       description = "Logrotate Service";
@@ -172,7 +387,16 @@ in
       serviceConfig = {
         Restart = "no";
         User = "root";
-        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${configFile}";
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${mailOption} ${cfg.configFile}";
+      };
+    };
+    systemd.services.logrotate-checkconf = {
+      description = "Logrotate configuration check";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate --debug ${cfg.configFile}";
       };
     };
   };
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index 0c9b38b44b2..f1e074587b3 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -192,7 +192,6 @@ in {
         log_dir = "/var/log/mailman";
         lock_dir = "$var_dir/lock";
         etc_dir = "/etc";
-        ext_dir = "$etc_dir/mailman.d";
         pid_file = "/run/mailman/master.pid";
       };
 
@@ -225,7 +224,14 @@ in {
               See <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>.
             '';
           };
-    in (lib.optionals cfg.enablePostfix [
+    in [
+      { assertion = cfg.webHosts != [];
+        message = ''
+          services.mailman.serve.enable requires there to be at least one entry
+          in services.mailman.webHosts.
+        '';
+      }
+    ] ++ (lib.optionals cfg.enablePostfix [
       { assertion = postfix.enable;
         message = ''
           Mailman's default NixOS configuration requires Postfix to be enabled.
@@ -275,15 +281,14 @@ in {
           globals().update(json.load(f))
     '';
 
-    services.nginx = mkIf cfg.serve.enable {
+    services.nginx = mkIf (cfg.serve.enable && cfg.webHosts != []) {
       enable = mkDefault true;
-      virtualHosts."${lib.head cfg.webHosts}" = {
-        serverAliases = cfg.webHosts;
+      virtualHosts = lib.genAttrs cfg.webHosts (webHost: {
         locations = {
           "/".extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
           "/static/".alias = webSettings.STATIC_ROOT + "/";
         };
-      };
+      });
     };
 
     environment.systemPackages = [ (pkgs.buildEnv {
@@ -313,7 +318,9 @@ in {
     systemd.services = {
       mailman = {
         description = "GNU Mailman Master Process";
-        after = [ "network.target" ];
+        before = lib.optional cfg.enablePostfix "postfix.service";
+        after = [ "network.target" ]
+          ++ lib.optional cfg.enablePostfix "postfix-setup.service";
         restartTriggers = [ config.environment.etc."mailman.cfg".source ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 23d3574ae27..da14b6eef7e 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -723,23 +723,10 @@ in
         { ${setgidGroup}.gid = config.ids.gids.postdrop;
         };
 
-      systemd.services.postfix =
-        { description = "Postfix mail server";
-
-          wantedBy = [ "multi-user.target" ];
-          after = [ "network.target" ];
-          path = [ pkgs.postfix ];
-
-          serviceConfig = {
-            Type = "forking";
-            Restart = "always";
-            PIDFile = "/var/lib/postfix/queue/pid/master.pid";
-            ExecStart = "${pkgs.postfix}/bin/postfix start";
-            ExecStop = "${pkgs.postfix}/bin/postfix stop";
-            ExecReload = "${pkgs.postfix}/bin/postfix reload";
-          };
-
-          preStart = ''
+      systemd.services.postfix-setup =
+        { description = "Setup for Postfix mail server";
+          serviceConfig.Type = "oneshot";
+          script = ''
             # Backwards compatibility
             if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
               mkdir -p /var/lib
@@ -777,6 +764,24 @@ in
           '';
         };
 
+      systemd.services.postfix =
+        { description = "Postfix mail server";
+
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" "postfix-setup.service" ];
+          requires = [ "postfix-setup.service" ];
+          path = [ pkgs.postfix ];
+
+          serviceConfig = {
+            Type = "forking";
+            Restart = "always";
+            PIDFile = "/var/lib/postfix/queue/pid/master.pid";
+            ExecStart = "${pkgs.postfix}/bin/postfix start";
+            ExecStop = "${pkgs.postfix}/bin/postfix stop";
+            ExecReload = "${pkgs.postfix}/bin/postfix reload";
+          };
+        };
+
       services.postfix.config = (mapAttrs (_: v: mkDefault v) {
         compatibility_level  = pkgs.postfix.version;
         mail_owner           = cfg.user;
diff --git a/nixos/modules/services/matrix/matrix-synapse.nix b/nixos/modules/services/matrix/matrix-synapse.nix
index c4d14dbd547..a498aff7a55 100644
--- a/nixos/modules/services/matrix/matrix-synapse.nix
+++ b/nixos/modules/services/matrix/matrix-synapse.nix
@@ -81,7 +81,7 @@ in {
     (mkRemovedOptionModule [ "services" "matrix-synapse" "verbose" ] "Use a log config instead." )
 
     # options that were moved into rfc42 style settigns
-    (mkRemovedOptionModule [ "services" "matrix-synapse" "app_service_config_files" ] "Use settings.app_service_config_Files instead" )
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "app_service_config_files" ] "Use settings.app_service_config_files instead" )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_args" ] "Use settings.database.args instead" )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_name" ] "Use settings.database.args.database instead" )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_type" ] "Use settings.database.name instead" )
@@ -141,7 +141,7 @@ in {
       enable = mkEnableOption "matrix.org synapse";
 
       configFile = mkOption {
-        type = types.str;
+        type = types.path;
         readOnly = true;
         description = ''
           Path to the configuration file on the target system. Useful to configure e.g. workers
diff --git a/nixos/modules/services/matrix/matrix-synapse.xml b/nixos/modules/services/matrix/matrix-synapse.xml
index cdc4b4de1a7..cf33957d58e 100644
--- a/nixos/modules/services/matrix/matrix-synapse.xml
+++ b/nixos/modules/services/matrix/matrix-synapse.xml
@@ -119,7 +119,7 @@ in {
     <link linkend="opt-services.matrix-synapse.settings.listeners">listeners</link> = [
       {
         <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_address</link> = [ "::1" ];
+        <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_addresses</link> = [ "::1" ];
         <link linkend="opt-services.matrix-synapse.settings.listeners._.type">type</link> = "http";
         <link linkend="opt-services.matrix-synapse.settings.listeners._.tls">tls</link> = false;
         <link linkend="opt-services.matrix-synapse.settings.listeners._.x_forwarded">x_forwarded</link> = true;
@@ -152,10 +152,10 @@ in {
 
   <para>
    If you want to run a server with public registration by anybody, you can
-   then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.enable_registration</link> =
+   then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.settings.enable_registration</link> =
    true;</literal>. Otherwise, or you can generate a registration secret with
    <command>pwgen -s 64 1</command> and set it with
-   <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>.
+   <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.settings.registration_shared_secret</link></option>.
    To create a new user or admin, run the following after you have set the secret
    and have rebuilt NixOS:
 <screen>
diff --git a/nixos/modules/services/misc/autorandr.nix b/nixos/modules/services/misc/autorandr.nix
index a65c5c9d11c..ef799e9ce3b 100644
--- a/nixos/modules/services/misc/autorandr.nix
+++ b/nixos/modules/services/misc/autorandr.nix
@@ -5,6 +5,243 @@ with lib;
 let
 
   cfg = config.services.autorandr;
+  hookType = types.lines;
+
+  matrixOf = n: m: elemType:
+  mkOptionType rec {
+    name = "matrixOf";
+    description =
+      "${toString n}×${toString m} matrix of ${elemType.description}s";
+    check = xss:
+      let listOfSize = l: xs: isList xs && length xs == l;
+      in listOfSize n xss
+      && all (xs: listOfSize m xs && all elemType.check xs) xss;
+    merge = mergeOneOption;
+    getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]);
+    getSubModules = elemType.getSubModules;
+    substSubModules = mod: matrixOf n m (elemType.substSubModules mod);
+    functor = (defaultFunctor name) // { wrapped = elemType; };
+  };
+
+  profileModule = types.submodule {
+    options = {
+      fingerprint = mkOption {
+        type = types.attrsOf types.str;
+        description = ''
+          Output name to EDID mapping.
+          Use <code>autorandr --fingerprint</code> to get current setup values.
+        '';
+        default = { };
+      };
+
+      config = mkOption {
+        type = types.attrsOf configModule;
+        description = "Per output profile configuration.";
+        default = { };
+      };
+
+      hooks = mkOption {
+        type = hooksModule;
+        description = "Profile hook scripts.";
+        default = { };
+      };
+    };
+  };
+
+  configModule = types.submodule {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        description = "Whether to enable the output.";
+        default = true;
+      };
+
+      crtc = mkOption {
+        type = types.nullOr types.ints.unsigned;
+        description = "Output video display controller.";
+        default = null;
+        example = 0;
+      };
+
+      primary = mkOption {
+        type = types.bool;
+        description = "Whether output should be marked as primary";
+        default = false;
+      };
+
+      position = mkOption {
+        type = types.str;
+        description = "Output position";
+        default = "";
+        example = "5760x0";
+      };
+
+      mode = mkOption {
+        type = types.str;
+        description = "Output resolution.";
+        default = "";
+        example = "3840x2160";
+      };
+
+      rate = mkOption {
+        type = types.str;
+        description = "Output framerate.";
+        default = "";
+        example = "60.00";
+      };
+
+      gamma = mkOption {
+        type = types.str;
+        description = "Output gamma configuration.";
+        default = "";
+        example = "1.0:0.909:0.833";
+      };
+
+      rotate = mkOption {
+        type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
+        description = "Output rotate configuration.";
+        default = null;
+        example = "left";
+      };
+
+      transform = mkOption {
+        type = types.nullOr (matrixOf 3 3 types.float);
+        default = null;
+        example = literalExpression ''
+          [
+            [ 0.6 0.0 0.0 ]
+            [ 0.0 0.6 0.0 ]
+            [ 0.0 0.0 1.0 ]
+          ]
+        '';
+        description = ''
+          Refer to
+          <citerefentry>
+            <refentrytitle>xrandr</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>
+          for the documentation of the transform matrix.
+        '';
+      };
+
+      dpi = mkOption {
+        type = types.nullOr types.ints.positive;
+        description = "Output DPI configuration.";
+        default = null;
+        example = 96;
+      };
+
+      scale = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            method = mkOption {
+              type = types.enum [ "factor" "pixel" ];
+              description = "Output scaling method.";
+              default = "factor";
+              example = "pixel";
+            };
+
+            x = mkOption {
+              type = types.either types.float types.ints.positive;
+              description = "Horizontal scaling factor/pixels.";
+            };
+
+            y = mkOption {
+              type = types.either types.float types.ints.positive;
+              description = "Vertical scaling factor/pixels.";
+            };
+          };
+        });
+        description = ''
+          Output scale configuration.
+          </para><para>
+          Either configure by pixels or a scaling factor. When using pixel method the
+          <citerefentry>
+            <refentrytitle>xrandr</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>
+          option
+          <parameter class="command">--scale-from</parameter>
+          will be used; when using factor method the option
+          <parameter class="command">--scale</parameter>
+          will be used.
+          </para><para>
+          This option is a shortcut version of the transform option and they are mutually
+          exclusive.
+        '';
+        default = null;
+        example = literalExpression ''
+          {
+            x = 1.25;
+            y = 1.25;
+          }
+        '';
+      };
+    };
+  };
+
+  hooksModule = types.submodule {
+    options = {
+      postswitch = mkOption {
+        type = types.attrsOf hookType;
+        description = "Postswitch hook executed after mode switch.";
+        default = { };
+      };
+
+      preswitch = mkOption {
+        type = types.attrsOf hookType;
+        description = "Preswitch hook executed before mode switch.";
+        default = { };
+      };
+
+      predetect = mkOption {
+        type = types.attrsOf hookType;
+        description = ''
+          Predetect hook executed before autorandr attempts to run xrandr.
+        '';
+        default = { };
+      };
+    };
+  };
+
+  hookToFile = folder: name: hook:
+    nameValuePair "xdg/autorandr/${folder}/${name}" {
+      source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
+    };
+  profileToFiles = name: profile:
+    with profile;
+    mkMerge ([
+      {
+        "xdg/autorandr/${name}/setup".text = concatStringsSep "\n"
+          (mapAttrsToList fingerprintToString fingerprint);
+        "xdg/autorandr/${name}/config".text =
+          concatStringsSep "\n" (mapAttrsToList configToString profile.config);
+      }
+      (mapAttrs' (hookToFile "${name}/postswitch.d") hooks.postswitch)
+      (mapAttrs' (hookToFile "${name}/preswitch.d") hooks.preswitch)
+      (mapAttrs' (hookToFile "${name}/predetect.d") hooks.predetect)
+    ]);
+  fingerprintToString = name: edid: "${name} ${edid}";
+  configToString = name: config:
+    if config.enable then
+      concatStringsSep "\n" ([ "output ${name}" ]
+        ++ optional (config.position != "") "pos ${config.position}"
+        ++ optional (config.crtc != null) "crtc ${toString config.crtc}"
+        ++ optional config.primary "primary"
+        ++ optional (config.dpi != null) "dpi ${toString config.dpi}"
+        ++ optional (config.gamma != "") "gamma ${config.gamma}"
+        ++ optional (config.mode != "") "mode ${config.mode}"
+        ++ optional (config.rate != "") "rate ${config.rate}"
+        ++ optional (config.rotate != null) "rotate ${config.rotate}"
+        ++ optional (config.transform != null) ("transform "
+          + concatMapStringsSep "," toString (flatten config.transform))
+        ++ optional (config.scale != null)
+        ((if config.scale.method == "factor" then "scale" else "scale-from")
+          + " ${toString config.scale.x}x${toString config.scale.y}"))
+    else ''
+      output ${name}
+      off
+    '';
 
 in {
 
@@ -22,6 +259,67 @@ in {
           for further reference.
         '';
       };
+
+      hooks = mkOption {
+        type = hooksModule;
+        description = "Global hook scripts";
+        default = { };
+        example = ''
+          {
+            postswitch = {
+              "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
+              "change-background" = readFile ./change-background.sh;
+              "change-dpi" = '''
+                case "$AUTORANDR_CURRENT_PROFILE" in
+                  default)
+                    DPI=120
+                    ;;
+                  home)
+                    DPI=192
+                    ;;
+                  work)
+                    DPI=144
+                    ;;
+                  *)
+                    echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE"
+                    exit 1
+                esac
+                echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
+              '''
+            };
+          }
+        '';
+      };
+      profiles = mkOption {
+        type = types.attrsOf profileModule;
+        description = "Autorandr profiles specification.";
+        default = { };
+        example = literalExpression ''
+          {
+            "work" = {
+              fingerprint = {
+                eDP1 = "<EDID>";
+                DP1 = "<EDID>";
+              };
+              config = {
+                eDP1.enable = false;
+                DP1 = {
+                  enable = true;
+                  crtc = 0;
+                  primary = true;
+                  position = "0x0";
+                  mode = "3840x2160";
+                  gamma = "1.0:0.909:0.833";
+                  rate = "60.00";
+                  rotate = "left";
+                };
+              };
+              hooks.postswitch = readFile ./work-postswitch.sh;
+            };
+          }
+        '';
+      };
+
     };
 
   };
@@ -30,7 +328,15 @@ in {
 
     services.udev.packages = [ pkgs.autorandr ];
 
-    environment.systemPackages = [ pkgs.autorandr ];
+    environment = {
+      systemPackages = [ pkgs.autorandr ];
+      etc = mkMerge ([
+        (mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch)
+        (mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch)
+        (mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect)
+        (mkMerge (mapAttrsToList profileToFiles cfg.profiles))
+      ]);
+    };
 
     systemd.services.autorandr = {
       wantedBy = [ "sleep.target" ];
@@ -49,5 +355,5 @@ in {
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = with maintainers; [ alexnortung ];
 }
diff --git a/nixos/modules/services/misc/dendrite.nix b/nixos/modules/services/misc/dendrite.nix
index b2885b09415..35bec40926e 100644
--- a/nixos/modules/services/misc/dendrite.nix
+++ b/nixos/modules/services/misc/dendrite.nix
@@ -247,15 +247,13 @@ in
         WorkingDirectory = workingDir;
         RuntimeDirectory = "dendrite";
         RuntimeDirectoryMode = "0700";
+        LimitNOFILE = 65535;
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-        ExecStartPre =
-          if (cfg.environmentFile != null) then ''
-            ${pkgs.envsubst}/bin/envsubst \
-              -i ${configurationYaml} \
-              -o /run/dendrite/dendrite.yaml
-          '' else ''
-            ${pkgs.coreutils}/bin/cp ${configurationYaml} /run/dendrite/dendrite.yaml
-          '';
+        ExecStartPre = ''
+          ${pkgs.envsubst}/bin/envsubst \
+            -i ${configurationYaml} \
+            -o /run/dendrite/dendrite.yaml
+        '';
         ExecStart = lib.strings.concatStringsSep " " ([
           "${pkgs.dendrite}/bin/dendrite-monolith-server"
           "--config /run/dendrite/dendrite.yaml"
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index dd84ac37b0d..cb99364aa1a 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -166,7 +166,7 @@ in
       } ''
         makeWrapper ${pythonEnv}/bin/etebase-server \
           $out/bin/etebase-server \
-          --run "cd ${cfg.dataDir}" \
+          --chdir ${escapeShellArg cfg.dataDir} \
           --prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}"
       '')
     ];
diff --git a/nixos/modules/services/misc/ethminer.nix b/nixos/modules/services/misc/ethminer.nix
index 95afb0460fb..22363466982 100644
--- a/nixos/modules/services/misc/ethminer.nix
+++ b/nixos/modules/services/misc/ethminer.nix
@@ -22,7 +22,7 @@ in
       };
 
       recheckInterval = mkOption {
-        type = types.int;
+        type = types.ints.unsigned;
         default = 2000;
         description = "Interval in milliseconds between farm rechecks.";
       };
@@ -70,7 +70,7 @@ in
       };
 
       maxPower = mkOption {
-        type = types.int;
+        type = types.ints.unsigned;
         default = 113;
         description = "Miner max watt usage.";
       };
@@ -85,7 +85,7 @@ in
   config = mkIf cfg.enable {
 
     systemd.services.ethminer = {
-      path = [ pkgs.cudatoolkit ];
+      path = optional (cfg.toolkit == "cuda") [ pkgs.cudaPackages.cudatoolkit ];
       description = "ethminer ethereum mining service";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
@@ -97,7 +97,7 @@ in
         Restart = "always";
       };
 
-      environment = {
+      environment = mkIf (cfg.toolkit == "cuda") {
         LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
       };
 
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index bc7bb663ee0..effa0c06ad6 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -499,6 +499,7 @@ in
         oldLfsJwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret"; # old file for LFS_JWT_SECRET
         lfsJwtSecret = "${cfg.stateDir}/custom/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
         internalToken = "${cfg.stateDir}/custom/conf/internal_token";
+        replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
       in ''
         # copy custom configuration and generate a random secret key if needed
         ${optionalString (cfg.useWizard == false) ''
@@ -526,23 +527,17 @@ in
                 ${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
             fi
 
-            SECRETKEY="$(head -n1 ${secretKey})"
-            DBPASS="$(head -n1 ${cfg.database.passwordFile})"
-            OAUTH2JWTSECRET="$(head -n1 ${oauth2JwtSecret})"
-            LFSJWTSECRET="$(head -n1 ${lfsJwtSecret})"
-            INTERNALTOKEN="$(head -n1 ${internalToken})"
-            ${if (cfg.mailerPasswordFile == null) then ''
-              MAILERPASSWORD="#mailerpass#"
-            '' else ''
-              MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
+            chmod u+w '${runConfig}'
+            ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
+            ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
+            ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
+            ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
+            ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
+
+            ${lib.optionalString (cfg.mailerPasswordFile != null) ''
+              ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
             ''}
-            sed -e "s,#secretkey#,$SECRETKEY,g" \
-                -e "s,#dbpass#,$DBPASS,g" \
-                -e "s,#oauth2jwtsecret#,$OAUTH2JWTSECRET,g" \
-                -e "s,#lfsjwtsecret#,$LFSJWTSECRET,g" \
-                -e "s,#internaltoken#,$INTERNALTOKEN,g" \
-                -e "s,#mailerpass#,$MAILERPASSWORD,g" \
-                -i ${runConfig}
+            chmod u-w '${runConfig}'
           }
           (umask 027; gitea_setup)
         ''}
diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix
index ceb186c0f04..87dd97166b8 100644
--- a/nixos/modules/services/misc/gitit.nix
+++ b/nixos/modules/services/misc/gitit.nix
@@ -10,7 +10,7 @@ let
 
   toYesNo = b: if b then "yes" else "no";
 
-  gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.hostPlatform.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version;
+  gititShared = with cfg.haskellPackages; gitit + "/share/" + ghc.targetPrefix + ghc.haskellCompilerName + "/" + gitit.pname + "-" + gitit.version;
 
   gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self));
 
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index e48444f7161..0811b34156e 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -179,7 +179,7 @@ let
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
           --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
           --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
-          --run 'cd ${cfg.packages.gitlab}/share/gitlab'
+          --chdir '${cfg.packages.gitlab}/share/gitlab'
      '';
   };
 
@@ -193,7 +193,7 @@ let
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
           --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
-          --run 'cd ${cfg.packages.gitlab}/share/gitlab'
+          --chdir '${cfg.packages.gitlab}/share/gitlab'
      '';
   };
 
@@ -848,10 +848,7 @@ in {
 
         extraConfig = mkOption {
           type = types.lines;
-          default = ''
-            copytruncate
-            compress
-          '';
+          default = "";
           description = ''
             Extra logrotate config options for this path. Refer to
             <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
@@ -977,13 +974,14 @@ in {
     # Enable rotation of log files
     services.logrotate = {
       enable = cfg.logrotate.enable;
-      paths = {
+      settings = {
         gitlab = {
-          path = "${cfg.statePath}/log/*.log";
-          user = cfg.user;
-          group = cfg.group;
+          files = "${cfg.statePath}/log/*.log";
+          su = "${cfg.user} ${cfg.group}";
           frequency = cfg.logrotate.frequency;
-          keep = cfg.logrotate.keep;
+          rotate = cfg.logrotate.keep;
+          copytruncate = true;
+          compress = true;
           extraConfig = cfg.logrotate.extraConfig;
         };
       };
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index ae57aaa6d47..b75227effa0 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -79,6 +79,19 @@ in {
           for supported values.
         '';
       };
+
+      allowSystemControl = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow Moonraker to perform system-level operations.
+
+          Moonraker exposes APIs to perform system-level operations, such as
+          reboot, shutdown, and management of systemd units. See the
+          <link xlink:href="https://moonraker.readthedocs.io/en/latest/web_api/#machine-commands">documentation</link>
+          for details on what clients are able to do.
+        '';
+      };
     };
   };
 
@@ -86,6 +99,13 @@ in {
     warnings = optional (cfg.settings ? update_manager)
       ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
 
+    assertions = [
+      {
+        assertion = cfg.allowSystemControl -> config.security.polkit.enable;
+        message = "services.moonraker.allowSystemControl requires polkit to be enabled (security.polkit.enable).";
+      }
+    ];
+
     users.users = optionalAttrs (cfg.user == "moonraker") {
       moonraker = {
         group = cfg.group;
@@ -128,11 +148,31 @@ in {
         exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
       '';
 
+      # Needs `ip` command
+      path = [ pkgs.iproute2 ];
+
       serviceConfig = {
         WorkingDirectory = cfg.stateDir;
         Group = cfg.group;
         User = cfg.user;
       };
     };
+
+    security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl ''
+      // nixos/moonraker: Allow Moonraker to perform system-level operations
+      //
+      // This was enabled via services.moonraker.allowSystemControl.
+      polkit.addRule(function(action, subject) {
+        if ((action.id == "org.freedesktop.systemd1.manage-units" ||
+             action.id == "org.freedesktop.login1.power-off" ||
+             action.id == "org.freedesktop.login1.power-off-multiple-sessions" ||
+             action.id == "org.freedesktop.login1.reboot" ||
+             action.id == "org.freedesktop.login1.reboot-multiple-sessions" ||
+             action.id.startsWith("org.freedesktop.packagekit.")) &&
+             subject.user == "${cfg.user}") {
+          return polkit.Result.YES;
+        }
+      });
+    '';
   };
 }
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 2b21df91b82..a4d2d10af70 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -112,11 +112,11 @@ in
 
 {
   imports = [
-    (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
-    (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
-    (mkRenamedOptionModule [ "nix" "daemonIONiceLevel" ] [ "nix" "daemonIOSchedPriority" ])
+    (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; })
+    (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; })
+    (mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" "daemonIONiceLevel" ]; to = [ "nix" "daemonIOSchedPriority" ]; })
     (mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
-  ] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModule [ "nix" oldConf ] [ "nix" "settings" newConf ]) legacyConfMappings;
+  ] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" oldConf ]; to = [ "nix" "settings" newConf ]; }) legacyConfMappings;
 
   ###### interface
 
@@ -409,14 +409,14 @@ in
               to = mkOption {
                 type = referenceAttrs;
                 example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
-                description = "The flake reference <option>from></option> is rewritten to.";
+                description = "The flake reference <option>from</option> is rewritten to.";
               };
               flake = mkOption {
                 type = types.nullOr types.attrs;
                 default = null;
                 example = literalExpression "nixpkgs";
                 description = ''
-                  The flake input <option>from></option> is rewritten to.
+                  The flake input <option>from</option> is rewritten to.
                 '';
               };
               exact = mkOption {
@@ -708,6 +708,14 @@ in
 
     systemd.packages = [ nixPackage ];
 
+    # Will only work once https://github.com/NixOS/nix/pull/6285 is merged
+    # systemd.tmpfiles.packages = [ nixPackage ];
+
+    # Can be dropped for Nix > https://github.com/NixOS/nix/pull/6285
+    systemd.tmpfiles.rules = [
+      "d /nix/var/nix/daemon-socket 0755 root root - -"
+    ];
+
     systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
 
     systemd.services.nix-daemon =
diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix
index a7a6a3b5964..0fcb0160101 100644
--- a/nixos/modules/services/misc/nix-gc.nix
+++ b/nixos/modules/services/misc/nix-gc.nix
@@ -39,7 +39,7 @@ in
         type = types.str;
         example = "45min";
         description = ''
-          Add a randomized delay before each automatic upgrade.
+          Add a randomized delay before each garbage collection.
           The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
@@ -81,8 +81,14 @@ in
   ###### implementation
 
   config = {
-
-    systemd.services.nix-gc = {
+    assertions = [
+      {
+        assertion = cfg.automatic -> config.nix.enable;
+        message = ''nix.gc.automatic requires nix.enable'';
+      }
+    ];
+
+    systemd.services.nix-gc = lib.mkIf config.nix.enable {
       description = "Nix Garbage Collector";
       script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
       startAt = optional cfg.automatic cfg.dates;
diff --git a/nixos/modules/services/misc/nix-optimise.nix b/nixos/modules/services/misc/nix-optimise.nix
index e02026d5f76..acf8177b146 100644
--- a/nixos/modules/services/misc/nix-optimise.nix
+++ b/nixos/modules/services/misc/nix-optimise.nix
@@ -37,8 +37,14 @@ in
   ###### implementation
 
   config = {
-
-    systemd.services.nix-optimise =
+    assertions = [
+      {
+        assertion = cfg.automatic -> config.nix.enable;
+        message = ''nix.optimise.automatic requires nix.enable'';
+      }
+    ];
+
+    systemd.services.nix-optimise = lib.mkIf config.nix.enable
       { description = "Nix Store Optimiser";
         # No point this if the nix daemon (and thus the nix store) is outside
         unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket";
diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless.nix
index 11e44f5ece5..bfaf842fb46 100644
--- a/nixos/modules/services/misc/paperless-ng.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -2,11 +2,13 @@
 
 with lib;
 let
-  cfg = config.services.paperless-ng;
+  cfg = config.services.paperless;
 
   defaultUser = "paperless";
 
-  hasCustomRedis = hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
+  # Don't start a redis instance if the user sets a custom redis connection
+  enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
+  redisServer = config.services.redis.servers.paperless;
 
   env = {
     PAPERLESS_DATA_DIR = cfg.dataDir;
@@ -15,15 +17,15 @@ let
     GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
   } // (
     lib.mapAttrs (_: toString) cfg.extraConfig
-  ) // (optionalAttrs (!hasCustomRedis) {
-    PAPERLESS_REDIS = "unix://${config.services.redis.servers.paperless-ng.unixSocket}";
+  ) // (optionalAttrs enableRedis {
+    PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
   });
 
   manage = let
     setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
   in pkgs.writeShellScript "manage" ''
     ${setupEnv}
-    exec ${cfg.package}/bin/paperless-ng "$@"
+    exec ${cfg.package}/bin/paperless-ngx "$@"
   '';
 
   # Secure the services
@@ -36,7 +38,7 @@ let
       "-/etc/hosts"
       "-/etc/localtime"
       "-/run/postgresql"
-    ] ++ (optional (!hasCustomRedis) config.services.redis.servers.paperless-ng.unixSocket);
+    ] ++ (optional enableRedis redisServer.unixSocket);
     BindPaths = [
       cfg.consumptionDir
       cfg.dataDir
@@ -53,7 +55,6 @@ let
     PrivateNetwork = true;
     PrivateTmp = true;
     PrivateUsers = true;
-    ProcSubset = "pid";
     ProtectClock = true;
     # Breaks if the home dir of the user is in /home
     # Also does not add much value in combination with the TemporaryFileSystem.
@@ -66,11 +67,15 @@ let
     ProtectKernelModules = true;
     ProtectKernelTunables = true;
     ProtectProc = "invisible";
+    # Don't restrict ProcSubset because django-q requires read access to /proc/stat
+    # to query CPU and memory information.
+    # Note that /proc only contains processes of user `paperless`, so this is safe.
+    # ProcSubset = "pid";
     RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
     RestrictNamespaces = true;
     RestrictRealtime = true;
     RestrictSUIDSGID = true;
-    SupplementaryGroups = optional (!hasCustomRedis) config.services.redis.servers.paperless-ng.user;
+    SupplementaryGroups = optional enableRedis redisServer.user;
     SystemCallArchitectures = "native";
     SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
     # Does not work well with the temporary root
@@ -81,26 +86,22 @@ in
   meta.maintainers = with maintainers; [ earvstedt Flakebi ];
 
   imports = [
-    (mkRemovedOptionModule [ "services" "paperless"] ''
-      The paperless module has been removed as the upstream project died.
-      Users should migrate to the paperless-ng module (services.paperless-ng).
-      More information can be found in the NixOS 21.11 release notes.
-    '')
+    (mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ])
   ];
 
-  options.services.paperless-ng = {
+  options.services.paperless = {
     enable = mkOption {
       type = lib.types.bool;
       default = false;
       description = ''
-        Enable Paperless-ng.
+        Enable Paperless.
 
         When started, the Paperless database is automatically created if it doesn't
         exist and updated if the Paperless package has changed.
         Both tasks are achieved by running a Django migration.
 
         A script to manage the Paperless instance (by wrapping Django's manage.py) is linked to
-        <literal>''${dataDir}/paperless-ng-manage</literal>.
+        <literal>''${dataDir}/paperless-manage</literal>.
       '';
     };
 
@@ -133,13 +134,13 @@ in
     passwordFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      example = "/run/keys/paperless-ng-password";
+      example = "/run/keys/paperless-password";
       description = ''
         A file containing the superuser password.
 
         A superuser is required to access the web interface.
         If unset, you can create a superuser manually by running
-        <literal>''${dataDir}/paperless-ng-manage createsuperuser</literal>.
+        <literal>''${dataDir}/paperless-manage createsuperuser</literal>.
 
         The default superuser name is <literal>admin</literal>. To change it, set
         option <option>extraConfig.PAPERLESS_ADMIN_USER</option>.
@@ -168,9 +169,9 @@ in
       type = types.attrs;
       default = {};
       description = ''
-        Extra paperless-ng config options.
+        Extra paperless config options.
 
-        See <link xlink:href="https://paperless-ng.readthedocs.io/en/latest/configuration.html">the documentation</link>
+        See <link xlink:href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html">the documentation</link>
         for available options.
       '';
       example = literalExpression ''
@@ -188,15 +189,14 @@ in
 
     package = mkOption {
       type = types.package;
-      default = pkgs.paperless-ng;
-      defaultText = literalExpression "pkgs.paperless-ng";
+      default = pkgs.paperless-ngx;
+      defaultText = literalExpression "pkgs.paperless-ngx";
       description = "The Paperless package to use.";
     };
   };
 
   config = mkIf cfg.enable {
-    # Enable redis if no special url is set
-    services.redis.servers.paperless-ng.enable = mkIf (!hasCustomRedis) true;
+    services.redis.servers.paperless.enable = mkIf enableRedis true;
 
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
@@ -208,26 +208,28 @@ in
       )
     ];
 
-    systemd.services.paperless-ng-server = {
-      description = "Paperless document server";
+    systemd.services.paperless-scheduler = {
+      description = "Paperless scheduler";
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ng qcluster";
+        ExecStart = "${cfg.package}/bin/paperless-ngx qcluster";
         Restart = "on-failure";
         # The `mbind` syscall is needed for running the classifier.
         SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
+        # Needs to talk to mail server for automated import rules
+        PrivateNetwork = false;
       };
       environment = env;
       wantedBy = [ "multi-user.target" ];
-      wants = [ "paperless-ng-consumer.service" "paperless-ng-web.service" ];
+      wants = [ "paperless-consumer.service" "paperless-web.service" ];
 
       preStart = ''
-        ln -sf ${manage} ${cfg.dataDir}/paperless-ng-manage
+        ln -sf ${manage} ${cfg.dataDir}/paperless-manage
 
         # Auto-migrate on first run or if the package has changed
         versionFile="${cfg.dataDir}/src-version"
         if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
-          ${cfg.package}/bin/paperless-ng migrate
+          ${cfg.package}/bin/paperless-ngx migrate
           echo ${cfg.package} > "$versionFile"
         fi
       ''
@@ -238,52 +240,48 @@ in
         superuserStateFile="${cfg.dataDir}/superuser-state"
 
         if [[ $(cat "$superuserStateFile" 2>/dev/null) != $superuserState ]]; then
-          ${cfg.package}/bin/paperless-ng manage_superuser
+          ${cfg.package}/bin/paperless-ngx manage_superuser
           echo "$superuserState" > "$superuserStateFile"
         fi
       '';
-    } // optionalAttrs (!hasCustomRedis) {
-      after = [ "redis-paperless-ng.service" ];
+    } // optionalAttrs enableRedis {
+      after = [ "redis-paperless.service" ];
     };
 
-    # Password copying can't be implemented as a privileged preStart script
-    # in 'paperless-ng-server' because 'defaultServiceConfig' limits the filesystem
-    # paths accessible by the service.
-    systemd.services.paperless-ng-copy-password = mkIf (cfg.passwordFile != null) {
-      requiredBy = [ "paperless-ng-server.service" ];
-      before = [ "paperless-ng-server.service" ];
+    # Reading the user-provided password file requires root access
+    systemd.services.paperless-copy-password = mkIf (cfg.passwordFile != null) {
+      requiredBy = [ "paperless-scheduler.service" ];
+      before = [ "paperless-scheduler.service" ];
       serviceConfig = {
         ExecStart = ''
           ${pkgs.coreutils}/bin/install --mode 600 --owner '${cfg.user}' --compare \
             '${cfg.passwordFile}' '${cfg.dataDir}/superuser-password'
         '';
         Type = "oneshot";
-        # Needs to talk to mail server for automated import rules
-        PrivateNetwork = false;
       };
     };
 
-    systemd.services.paperless-ng-consumer = {
+    systemd.services.paperless-consumer = {
       description = "Paperless document consumer";
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ng document_consumer";
+        ExecStart = "${cfg.package}/bin/paperless-ngx document_consumer";
         Restart = "on-failure";
       };
       environment = env;
-      # Bind to `paperless-ng-server` so that the consumer never runs
+      # Bind to `paperless-scheduler` so that the consumer never runs
       # during migrations
-      bindsTo = [ "paperless-ng-server.service" ];
-      after = [ "paperless-ng-server.service" ];
+      bindsTo = [ "paperless-scheduler.service" ];
+      after = [ "paperless-scheduler.service" ];
     };
 
-    systemd.services.paperless-ng-web = {
+    systemd.services.paperless-web = {
       description = "Paperless web server";
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
         ExecStart = ''
           ${pkgs.python3Packages.gunicorn}/bin/gunicorn \
-            -c ${cfg.package}/lib/paperless-ng/gunicorn.conf.py paperless.asgi:application
+            -c ${cfg.package}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
         '';
         Restart = "on-failure";
 
@@ -296,15 +294,15 @@ in
       };
       environment = env // {
         PATH = mkForce cfg.package.path;
-        PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ng/src";
+        PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ngx/src";
       };
       # Allow the web interface to access the private /tmp directory of the server.
       # This is required to support uploading files via the web interface.
-      unitConfig.JoinsNamespaceOf = "paperless-ng-server.service";
-      # Bind to `paperless-ng-server` so that the web server never runs
+      unitConfig.JoinsNamespaceOf = "paperless-scheduler.service";
+      # Bind to `paperless-scheduler` so that the web server never runs
       # during migrations
-      bindsTo = [ "paperless-ng-server.service" ];
-      after = [ "paperless-ng-server.service" ];
+      bindsTo = [ "paperless-scheduler.service" ];
+      after = [ "paperless-scheduler.service" ];
     };
 
     users = optionalAttrs (cfg.user == defaultUser) {
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index 21551d7d5f0..5a6d011a729 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -1018,7 +1018,7 @@ in
       inherit configIniOfService;
       mainService = mkMerge [ baseService {
         serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
-        preStart = mkIf (!versionAtLeast config.system.stateVersion "22.05") (mkBefore ''
+        preStart = mkIf (versionOlder config.system.stateVersion "22.05") (mkBefore ''
           # Fix Git hooks of repositories pre-dating https://github.com/NixOS/nixpkgs/pull/133984
           (
           set +f
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index ff63c41e193..e2080492998 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -106,7 +106,7 @@ let
 
   certtool = "${pkgs.gnutls.bin}/bin/certtool";
 
-  nixos-taskserver = with pkgs.python2.pkgs; buildPythonApplication {
+  nixos-taskserver = with pkgs.python3.pkgs; buildPythonApplication {
     name = "nixos-taskserver";
 
     src = pkgs.runCommand "nixos-taskserver-src" { preferLocalBuild = true; } ''
@@ -277,10 +277,6 @@ in {
         example = "::";
         description = ''
           The address (IPv4, IPv6 or DNS) to listen on.
-
-          If the value is something else than <literal>localhost</literal> the
-          port defined by <option>listenPort</option> is automatically added to
-          <option>networking.firewall.allowedTCPPorts</option>.
         '';
       };
 
@@ -292,6 +288,14 @@ in {
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the firewall for the specified Taskserver port.
+        '';
+      };
+
       fqdn = mkOption {
         type = types.str;
         default = "localhost";
@@ -560,7 +564,7 @@ in {
         '';
       };
     })
-    (mkIf (cfg.enable && cfg.listenHost != "localhost") {
+    (mkIf (cfg.enable && cfg.openFirewall) {
       networking.firewall.allowedTCPPorts = [ cfg.listenPort ];
     })
   ];
diff --git a/nixos/modules/services/misc/taskserver/helper-tool.py b/nixos/modules/services/misc/taskserver/helper-tool.py
index 22a3d8d5311..fec05728b2b 100644
--- a/nixos/modules/services/misc/taskserver/helper-tool.py
+++ b/nixos/modules/services/misc/taskserver/helper-tool.py
@@ -90,7 +90,7 @@ def certtool_cmd(*args, **kwargs):
     """
     return subprocess.check_output(
         [CERTTOOL_COMMAND] + list(args),
-        preexec_fn=lambda: os.umask(0077),
+        preexec_fn=lambda: os.umask(0o077),
         stderr=subprocess.STDOUT,
         **kwargs
     )
@@ -164,7 +164,7 @@ def generate_key(org, user):
     pubcert = os.path.join(basedir, "public.cert")
 
     try:
-        os.makedirs(basedir, mode=0700)
+        os.makedirs(basedir, mode=0o700)
 
         certtool_cmd("-p", "--bits", CERT_BITS, "--outfile", privkey)
 
@@ -301,7 +301,7 @@ class Organisation(object):
             return None
         if name not in self.users.keys():
             output = taskd_cmd("add", "user", self.name, name,
-                               capture_stdout=True)
+                               capture_stdout=True, encoding='utf-8')
             key = RE_USERKEY.search(output)
             if key is None:
                 msg = "Unable to find key while creating user {}."
@@ -412,9 +412,9 @@ class Manager(object):
         if org is not None:
             if self.ignore_imperative and is_imperative(name):
                 return
-            for user in org.users.keys():
+            for user in list(org.users.keys()):
                 org.del_user(user)
-            for group in org.groups.keys():
+            for group in list(org.groups.keys()):
                 org.del_group(group)
             taskd_cmd("remove", "org", name)
             del self._lazy_orgs[name]
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 8d81737a3ef..1b9af585756 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -5,36 +5,15 @@ with lib;
 let
   cfg = config.services.collectd;
 
-  unvalidated_conf = pkgs.writeText "collectd-unvalidated.conf" ''
-    BaseDir "${cfg.dataDir}"
-    AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
-    Hostname "${config.networking.hostName}"
-
-    LoadPlugin syslog
-    <Plugin "syslog">
-      LogLevel "info"
-      NotifyLevel "OKAY"
-    </Plugin>
-
-    ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
-      LoadPlugin ${plugin}
-      <Plugin "${plugin}">
-      ${pluginConfig}
-      </Plugin>
-    '') cfg.plugins)}
-
-    ${concatMapStrings (f: ''
-      Include "${f}"
-    '') cfg.include}
-
-    ${cfg.extraConfig}
-  '';
+  baseDirLine = ''BaseDir "${cfg.dataDir}"'';
+  unvalidated_conf = pkgs.writeText "collectd-unvalidated.conf" cfg.extraConfig;
 
   conf = if cfg.validateConfig then
     pkgs.runCommand "collectd.conf" {} ''
       echo testing ${unvalidated_conf}
+      cp ${unvalidated_conf} collectd.conf
       # collectd -t fails if BaseDir does not exist.
-      sed '1s/^BaseDir.*$/BaseDir "."/' ${unvalidated_conf} > collectd.conf
+      substituteInPlace collectd.conf --replace ${lib.escapeShellArgs [ baseDirLine ]} 'BaseDir "."'
       ${package}/bin/collectd -t -C collectd.conf
       cp ${unvalidated_conf} $out
     '' else unvalidated_conf;
@@ -123,7 +102,8 @@ in {
     extraConfig = mkOption {
       default = "";
       description = ''
-        Extra configuration for collectd.
+        Extra configuration for collectd. Use mkBefore to add lines before the
+        default config, and mkAfter to add them below.
       '';
       type = lines;
     };
@@ -131,6 +111,30 @@ in {
   };
 
   config = mkIf cfg.enable {
+    # 1200 is after the default (1000) but before mkAfter (1500).
+    services.collectd.extraConfig = lib.mkOrder 1200 ''
+      ${baseDirLine}
+      AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
+      Hostname "${config.networking.hostName}"
+
+      LoadPlugin syslog
+      <Plugin "syslog">
+        LogLevel "info"
+        NotifyLevel "OKAY"
+      </Plugin>
+
+      ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
+        LoadPlugin ${plugin}
+        <Plugin "${plugin}">
+        ${pluginConfig}
+        </Plugin>
+      '') cfg.plugins)}
+
+      ${concatMapStrings (f: ''
+        Include "${f}"
+      '') cfg.include}
+    '';
+
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' - ${cfg.user} - - -"
     ];
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 81fca33f5fe..b959379d331 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -214,6 +214,11 @@ let
           type = types.path;
           description = "Path grafana will watch for dashboards.";
         };
+        foldersFromFilesStructure = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Use folder names from filesystem to create folders in Grafana.";
+        };
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 2c7f0ed1966..69173ce4e44 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -102,8 +102,8 @@ in
 
       plugins = mkOption {
         type = types.listOf types.package;
-        default = with pkgs; [ monitoring-plugins ssmtp mailutils ];
-        defaultText = literalExpression "[pkgs.monitoring-plugins pkgs.ssmtp pkgs.mailutils]";
+        default = with pkgs; [ monitoring-plugins msmtp mailutils ];
+        defaultText = literalExpression "[pkgs.monitoring-plugins pkgs.msmtp pkgs.mailutils]";
         description = "
           Packages to be added to the Nagios <envar>PATH</envar>.
           Typically used to add plugins, but can be anything.
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index f563861b61c..52525e8935b 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -74,7 +74,6 @@ let
     }"
     "--web.listen-address=${cfg.listenAddress}:${builtins.toString cfg.port}"
     "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
-    "--alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
   ] ++ optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}"
     ++ optional (cfg.retentionTime != null) "--storage.tsdb.retention.time=${cfg.retentionTime}";
 
@@ -1563,6 +1562,8 @@ in
     (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ])
     (mkRemovedOptionModule [ "services" "prometheus" "environmentFile" ]
       "It has been removed since it was causing issues (https://github.com/NixOS/nixpkgs/issues/126083) and Prometheus now has native support for secret files, i.e. `basic_auth.password_file` and `authorization.credentials_file`.")
+    (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerTimeout" ]
+      "Deprecated upstream and no longer had any effect")
   ];
 
   options.services.prometheus = {
@@ -1719,14 +1720,6 @@ in
       '';
     };
 
-    alertmanagerTimeout = mkOption {
-      type = types.int;
-      default = 10;
-      description = ''
-        Alert manager HTTP API timeout (in seconds).
-      '';
-    };
-
     webExternalUrl = mkOption {
       type = types.nullOr types.str;
       default = null;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index c2d4b05996a..1df88bb61a1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -19,6 +19,7 @@
 <programlisting>
   services.prometheus.exporters.node = {
     enable = true;
+    port = 9100;
     enabledCollectors = [
       "logind"
       "systemd"
@@ -42,6 +43,26 @@
    <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
    options</link>.
   </para>
+
+  <para>
+    Prometheus can now be configured to consume the metrics produced by the exporter:
+    <programlisting>
+    services.prometheus = {
+      # ...
+
+      scrapeConfigs = [
+        {
+          job_name = "node";
+          static_configs = [{
+            targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ];
+          }];
+        }
+      ];
+
+      # ...
+    }
+    </programlisting>
+  </para>
  </section>
  <section xml:id="module-services-prometheus-exporters-new-exporter">
   <title>Adding a new exporter</title>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
index 1ef264fc86e..5fda4c980eb 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
@@ -17,7 +17,7 @@ in
     };
     birdSocket = mkOption {
       type = types.path;
-      default = "/var/run/bird.ctl";
+      default = "/run/bird/bird.ctl";
       description = ''
         Path to BIRD2 (or BIRD1 v4) socket.
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index 27aeb909624..e0ee90d9b97 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -25,6 +25,10 @@ in {
     };
   };
   serviceOpts = {
+    after = [
+      "kea-dhcp4-server.service"
+      "kea-dhcp6-server.service"
+    ];
     serviceConfig = {
       User = "kea";
       ExecStart = ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index 5b5a6e18fcd..ede6028933a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -45,7 +45,8 @@ in
     };
     instance = mkOption {
       type = types.nullOr types.str;
-      default = null;
+      default = config.services.varnish.stateDir;
+      defaultText = lib.literalExpression "config.services.varnish.stateDir";
       description = ''
         varnishstat -n value.
       '';
@@ -66,7 +67,7 @@ in
     };
   };
   serviceOpts = {
-    path = [ pkgs.varnish ];
+    path = [ config.services.varnish.package ];
     serviceConfig = {
       RestartSec = mkDefault 1;
       DynamicUser = false;
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 17da020bf3e..395b9788855 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -1,16 +1,16 @@
-{ config, lib, pkgs, options, ... }:
+{ config, lib, pkgs, utils, ... }:
 with lib;
 let
   cfg = config.services.ipfs;
-  opt = options.services.ipfs;
 
-  ipfsFlags = toString ([
-    (optionalString cfg.autoMount "--mount")
-    (optionalString cfg.enableGC "--enable-gc")
-    (optionalString (cfg.serviceFdlimit != null) "--manage-fdlimit=false")
-    (optionalString (cfg.defaultMode == "offline") "--offline")
-    (optionalString (cfg.defaultMode == "norouting") "--routing=none")
-  ] ++ cfg.extraFlags);
+  ipfsFlags = utils.escapeSystemdExecArgs (
+    optional cfg.autoMount "--mount" ++
+    optional cfg.enableGC "--enable-gc" ++
+    optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
+    optional (cfg.defaultMode == "offline") "--offline" ++
+    optional (cfg.defaultMode == "norouting") "--routing=none" ++
+    cfg.extraFlags
+  );
 
   profile =
     if cfg.localDiscovery
@@ -239,7 +239,10 @@ in
       "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
     ];
 
-    systemd.packages = [ cfg.package ];
+    # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
+    systemd.packages = if cfg.autoMount
+      then [ cfg.package.systemd_unit ]
+      else [ cfg.package.systemd_unit_hardened ];
 
     systemd.services.ipfs = {
       path = [ "/run/wrappers" cfg.package ];
@@ -251,23 +254,27 @@ in
         else
           # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
           rm -vf "$IPFS_PATH/api"
-
+      '' + optionalString cfg.autoMigrate ''
+        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
+      '' + ''
           ipfs --offline config profile apply ${profile}
         fi
       '' + optionalString cfg.autoMount ''
         ipfs --offline config Mounts.FuseAllowOther --json true
         ipfs --offline config Mounts.IPFS ${cfg.ipfsMountDir}
         ipfs --offline config Mounts.IPNS ${cfg.ipnsMountDir}
-      '' + optionalString cfg.autoMigrate ''
-        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
       '' + ''
         ipfs --offline config show \
           | ${pkgs.jq}/bin/jq '. * $extraConfig' --argjson extraConfig ${
-              escapeShellArg (builtins.toJSON ({
-                Addresses.API = cfg.apiAddress;
-                Addresses.Gateway = cfg.gatewayAddress;
-                Addresses.Swarm = cfg.swarmAddress;
-              } // cfg.extraConfig))
+              escapeShellArg (builtins.toJSON (
+                recursiveUpdate
+                  {
+                    Addresses.API = cfg.apiAddress;
+                    Addresses.Gateway = cfg.gatewayAddress;
+                    Addresses.Swarm = cfg.swarmAddress;
+                  }
+                  cfg.extraConfig
+              ))
             } \
           | ipfs --offline config replace -
       '';
@@ -275,6 +282,8 @@ in
         ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${ipfsFlags}" ];
         User = cfg.user;
         Group = cfg.group;
+        StateDirectory = "";
+        ReadWritePaths = [ "" cfg.dataDir ];
       } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
     } // optionalAttrs (!cfg.startWhenNeeded) {
       wantedBy = [ "default.target" ];
@@ -306,6 +315,9 @@ in
         in
         [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
     };
+  };
 
+  meta = {
+    maintainers = with lib.maintainers; [ Luflosi ];
   };
 }
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 9ed755d0465..992f948e8cd 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -224,6 +224,7 @@ in
           targets.samba = {
             description = "Samba Server";
             after = [ "network.target" ];
+            wants = [ "network-online.target" ];
             wantedBy = [ "multi-user.target" ];
           };
           # Refer to https://github.com/samba-team/samba/tree/master/packaging/systemd
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index 3049c4f2bce..d409f060228 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -68,8 +68,7 @@ in
     systemd.services.bird2 = {
       description = "BIRD Internet Routing Daemon";
       wantedBy = [ "multi-user.target" ];
-      reloadIfChanged = true;
-      restartTriggers = [ config.environment.etc."bird/bird2.conf".source ];
+      reloadTriggers = [ config.environment.etc."bird/bird2.conf".source ];
       serviceConfig = {
         Type = "forking";
         Restart = "on-failure";
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index ca9c422e6d7..cb53cc01f52 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -80,13 +80,21 @@ in
             The name of the interface to pull the bind_addr from.
           '';
         };
+      };
 
+      forceAddrFamily = mkOption {
+        type = types.enum [ "any" "ipv4" "ipv6" ];
+        default = "any";
+        description = ''
+          Whether to bind ipv4/ipv6 or both kind of addresses.
+        '';
       };
 
       forceIpv4 = mkOption {
-        type = types.bool;
-        default = false;
+        type = types.nullOr types.bool;
+        default = null;
         description = ''
+          Deprecated: Use consul.forceAddrFamily instead.
           Whether we should force the interfaces to only pull ipv4 addresses.
         '';
       };
@@ -175,6 +183,13 @@ in
         systemPackages = [ cfg.package ];
       };
 
+      warnings = lib.flatten [
+        (lib.optional (cfg.forceIpv4 != null) ''
+          The option consul.forceIpv4 is deprecated, please use
+          consul.forceAddrFamily instead.
+        '')
+      ];
+
       systemd.services.consul = {
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ] ++ systemdDevices;
@@ -196,15 +211,21 @@ in
         });
 
         path = with pkgs; [ iproute2 gnugrep gawk consul ];
-        preStart = ''
+        preStart = let
+          family = if cfg.forceAddrFamily == "ipv6" then
+            "-6"
+          else if cfg.forceAddrFamily == "ipv4" then
+            "-4"
+          else
+            "";
+        in ''
           mkdir -m 0700 -p ${dataDir}
           chown -R consul ${dataDir}
 
           # Determine interface addresses
           getAddrOnce () {
-            ip addr show dev "$1" \
-              | grep 'inet${optionalString (cfg.forceIpv4) " "}.*scope global' \
-              | awk -F '[ /\t]*' '{print $3}' | head -n 1
+            ip ${family} addr show dev "$1" scope global \
+              | awk -F '[ /\t]*' '/inet/ {print $3}' | head -n 1
           }
           getAddr () {
             ADDR="$(getAddrOnce $1)"
@@ -234,6 +255,11 @@ in
       };
     }
 
+    # deprecated
+    (mkIf (cfg.forceIpv4 != null && cfg.forceIpv4) {
+      services.consul.forceAddrFamily = "ipv4";
+    })
+
     (mkIf (cfg.alerts.enable) {
       systemd.services.consul-alerts = {
         wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/create_ap.nix b/nixos/modules/services/networking/create_ap.nix
new file mode 100644
index 00000000000..a3c330fab00
--- /dev/null
+++ b/nixos/modules/services/networking/create_ap.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.create_ap;
+  configFile = pkgs.writeText "create_ap.conf" (generators.toKeyValue { } cfg.settings);
+in {
+  options = {
+    services.create_ap = {
+      enable = mkEnableOption "setup wifi hotspots using create_ap";
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ int bool str ]);
+        default = {};
+        description = ''
+          Configuration for <package>create_ap</package>.
+          See <link xlink:href="https://raw.githubusercontent.com/lakinduakash/linux-wifi-hotspot/master/src/scripts/create_ap.conf">upstream example configuration</link>
+          for supported values.
+        '';
+        example = {
+          INTERNET_IFACE = "eth0";
+          WIFI_IFACE = "wlan0";
+          SSID = "My Wifi Hotspot";
+          PASSPHRASE = "12345678";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      services.create_ap = {
+        wantedBy = [ "multi-user.target" ];
+        description = "Create AP Service";
+        after = [ "network.target" ];
+        restartTriggers = [ configFile ];
+        serviceConfig = {
+          ExecStart = "${pkgs.linux-wifi-hotspot}/bin/create_ap --config ${configFile}";
+          KillSignal = "SIGINT";
+          Restart = "on-failure";
+        };
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 3c4c0069dfd..49950efc0a1 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -7,7 +7,7 @@ let
   cfg4 = config.services.dhcpd4;
   cfg6 = config.services.dhcpd6;
 
-  writeConfig = cfg: pkgs.writeText "dhcpd.conf"
+  writeConfig = postfix: cfg: pkgs.writeText "dhcpd.conf"
     ''
       default-lease-time 600;
       max-lease-time 7200;
@@ -21,7 +21,9 @@ let
           (machine: ''
             host ${machine.hostName} {
               hardware ethernet ${machine.ethernetAddress};
-              fixed-address ${machine.ipAddress};
+              fixed-address${
+                optionalString (postfix == "6") postfix
+              } ${machine.ipAddress};
             }
           '')
           cfg.machines
@@ -33,7 +35,7 @@ let
       configFile =
         if cfg.configFile != null
           then cfg.configFile
-          else writeConfig cfg;
+          else writeConfig postfix cfg;
       leaseFile = "/var/lib/dhcpd${postfix}/dhcpd.leases";
       args = [
         "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
diff --git a/nixos/modules/services/networking/envoy.nix b/nixos/modules/services/networking/envoy.nix
new file mode 100644
index 00000000000..b7f859c73d9
--- /dev/null
+++ b/nixos/modules/services/networking/envoy.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.envoy;
+  format = pkgs.formats.json { };
+  conf = format.generate "envoy.json" cfg.settings;
+  validateConfig = file:
+    pkgs.runCommand "validate-envoy-conf" { } ''
+      ${pkgs.envoy}/bin/envoy --log-level error --mode validate -c "${file}"
+      cp "${file}" "$out"
+    '';
+
+in
+
+{
+  options.services.envoy = {
+    enable = mkEnableOption "Envoy reverse proxy";
+
+    settings = mkOption {
+      type = format.type;
+      default = { };
+      example = literalExpression ''
+        {
+          admin = {
+            access_log_path = "/dev/null";
+            address = {
+              socket_address = {
+                protocol = "TCP";
+                address = "127.0.0.1";
+                port_value = 9901;
+              };
+            };
+          };
+          static_resources = {
+            listeners = [];
+            clusters = [];
+          };
+        }
+      '';
+      description = ''
+        Specify the configuration for Envoy in Nix.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.envoy ];
+    systemd.services.envoy = {
+      description = "Envoy reverse proxy";
+      after = [ "network-online.target" ];
+      requires = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.envoy}/bin/envoy -c ${validateConfig conf}";
+        DynamicUser = true;
+        Restart = "no";
+        CacheDirectory = "envoy";
+        LogsDirectory = "envoy";
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK AF_XDP";
+        SystemCallArchitectures = "native";
+        LockPersonality = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        PrivateUsers = false;  # breaks CAP_NET_BIND_SERVICE
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "ptraceable";
+        ProtectHostname = true;
+        ProtectSystem = "strict";
+        UMask = "0066";
+        SystemCallFilter = "~@clock @module @mount @reboot @swap @obsolete @cpu-emulation";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 091d2a938cd..5b07beadb45 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -479,7 +479,7 @@ in
           NoNewPrivileges = true;
           LockPersonality = true;
           RestrictRealtime = true;
-          SystemCallFilter = [ "@system-service" "~@priviledged" "@chown" ];
+          SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
           SystemCallArchitectures = "native";
           RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
         };
diff --git a/nixos/modules/services/networking/https-dns-proxy.nix b/nixos/modules/services/networking/https-dns-proxy.nix
new file mode 100644
index 00000000000..85d6c362b46
--- /dev/null
+++ b/nixos/modules/services/networking/https-dns-proxy.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib)
+    concatStringsSep
+    mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.https-dns-proxy;
+
+  providers = {
+    cloudflare = {
+      ips = [ "1.1.1.1" "1.0.0.1" ];
+      url = "https://cloudflare-dns.com/dns-query";
+    };
+    google = {
+      ips = [ "8.8.8.8" "8.8.4.4" ];
+      url = "https://dns.google/dns-query";
+    };
+    quad9 = {
+      ips = [ "9.9.9.9" "149.112.112.112" ];
+      url = "https://dns.quad9.net/dns-query";
+    };
+  };
+
+  defaultProvider = "quad9";
+
+  providerCfg =
+    let
+      isCustom = cfg.provider.kind == "custom";
+    in
+    lib.concatStringsSep " " [
+      "-b"
+      (concatStringsSep "," (if isCustom then cfg.provider.ips else providers."${cfg.provider.kind}".ips))
+      "-r"
+      (if isCustom then cfg.provider.url else providers."${cfg.provider.kind}".url)
+    ];
+
+in
+{
+  meta.maintainers = with lib.maintainers; [ peterhoeg ];
+
+  ###### interface
+
+  options.services.https-dns-proxy = {
+    enable = mkEnableOption "https-dns-proxy daemon";
+
+    address = mkOption {
+      description = "The address on which to listen";
+      type = types.str;
+      default = "127.0.0.1";
+    };
+
+    port = mkOption {
+      description = "The port on which to listen";
+      type = types.port;
+      default = 5053;
+    };
+
+    provider = {
+      kind = mkOption {
+        description = ''
+          The upstream provider to use or custom in case you do not trust any of
+          the predefined providers or just want to use your own.
+
+          The default is ${defaultProvider} and there are privacy and security trade-offs
+          when using any upstream provider. Please consider that before using any
+          of them.
+
+          If you pick a custom provider, you will need to provide the bootstrap
+          IP addresses as well as the resolver https URL.
+        '';
+        type = types.enum ((builtins.attrNames providers) ++ [ "custom" ]);
+        default = defaultProvider;
+      };
+
+      ips = mkOption {
+        description = "The custom provider IPs";
+        type = types.listOf types.str;
+      };
+
+      url = mkOption {
+        description = "The custom provider URL";
+        type = types.str;
+      };
+    };
+
+    preferIPv4 = mkOption {
+      description = ''
+        https_dns_proxy will by default use IPv6 and fail if it is not available.
+        To play it safe, we choose IPv4.
+      '';
+      type = types.bool;
+      default = true;
+    };
+
+    extraArgs = mkOption {
+      description = "Additional arguments to pass to the process.";
+      type = types.listOf types.str;
+      default = [ "-v" ];
+    };
+  };
+
+  ###### implementation
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.https-dns-proxy = {
+      description = "DNS to DNS over HTTPS (DoH) proxy";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        Type = "exec";
+        DynamicUser = true;
+        ExecStart = lib.concatStringsSep " " (
+          [
+            "${pkgs.https-dns-proxy}/bin/https_dns_proxy"
+            "-a ${toString cfg.address}"
+            "-p ${toString cfg.port}"
+            "-l -"
+            providerCfg
+          ]
+          ++ lib.optional cfg.preferIPv4 "-4"
+          ++ cfg.extraArgs
+        );
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index 8835f7f9372..5c1480e7e2f 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/nixos/modules/services/networking/iwd.nix
@@ -1,12 +1,21 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    mkEnableOption mkIf mkOption types
+    recursiveUpdate;
+
   cfg = config.networking.wireless.iwd;
   ini = pkgs.formats.ini { };
-  configFile = ini.generate "main.conf" cfg.settings;
-in {
+  defaults = {
+    # without UseDefaultInterface, sometimes wlan0 simply goes AWOL with NetworkManager
+    # https://iwd.wiki.kernel.org/interface_lifecycle#interface_management_in_iwd
+    General.UseDefaultInterface = with config.networking.networkmanager; (enable && (wifi.backend == "iwd"));
+  };
+  configFile = ini.generate "main.conf" (recursiveUpdate defaults cfg.settings);
+
+in
+{
   options.networking.wireless.iwd = {
     enable = mkEnableOption "iwd";
 
@@ -38,10 +47,10 @@ in {
       '';
     }];
 
-    environment.etc."iwd/main.conf".source = configFile;
+    environment.etc."iwd/${configFile.name}".source = configFile;
 
     # for iwctl
-    environment.systemPackages =  [ pkgs.iwd ];
+    environment.systemPackages = [ pkgs.iwd ];
 
     services.dbus.packages = [ pkgs.iwd ];
 
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 17b4eb2e283..994c511bdc2 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -9,20 +9,26 @@ with lib;
 let
   cfg = config.services.kea;
 
+  xor = x: y: (!x && y) || (x && !y);
   format = pkgs.formats.json {};
 
-  ctrlAgentConfig = format.generate "kea-ctrl-agent.conf" {
+  chooseNotNull = x: y: if x != null then x else y;
+
+  ctrlAgentConfig = chooseNotNull cfg.ctrl-agent.configFile (format.generate "kea-ctrl-agent.conf" {
     Control-agent = cfg.ctrl-agent.settings;
-  };
-  dhcp4Config = format.generate "kea-dhcp4.conf" {
+  });
+
+  dhcp4Config = chooseNotNull cfg.dhcp4.configFile (format.generate "kea-dhcp4.conf" {
     Dhcp4 = cfg.dhcp4.settings;
-  };
-  dhcp6Config = format.generate "kea-dhcp6.conf" {
+  });
+
+  dhcp6Config = chooseNotNull cfg.dhcp6.configFile (format.generate "kea-dhcp6.conf" {
     Dhcp6 = cfg.dhcp6.settings;
-  };
-  dhcpDdnsConfig = format.generate "kea-dhcp-ddns.conf" {
+  });
+
+  dhcpDdnsConfig = chooseNotNull cfg.dhcp-ddns.configFile (format.generate "kea-dhcp-ddns.conf" {
     DhcpDdns = cfg.dhcp-ddns.settings;
-  };
+  });
 
   package = pkgs.kea;
 in
@@ -45,6 +51,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea Control Agent configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.ctrl-agent.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.ctrl-agent.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -73,6 +90,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP4 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp4.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp4.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -122,6 +150,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP6 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp6.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp6.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -172,6 +211,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP-DDNS configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -214,6 +264,10 @@ in
   }
 
   (mkIf cfg.ctrl-agent.enable {
+    assertions = [{
+        assertion = xor (cfg.ctrl-agent.settings == null) (cfg.ctrl-agent.configFile == null);
+        message = "Either services.kea.ctrl-agent.settings or services.kea.ctrl-agent.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/ctrl-agent.conf".source = ctrlAgentConfig;
 
@@ -252,6 +306,10 @@ in
   })
 
   (mkIf cfg.dhcp4.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp4.settings == null) (cfg.dhcp4.configFile == null);
+        message = "Either services.kea.dhcp4.settings or services.kea.dhcp4.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp4-server.conf".source = dhcp4Config;
 
@@ -295,6 +353,10 @@ in
   })
 
   (mkIf cfg.dhcp6.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp6.settings == null) (cfg.dhcp6.configFile == null);
+        message = "Either services.kea.dhcp6.settings or services.kea.dhcp6.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp6-server.conf".source = dhcp6Config;
 
@@ -336,6 +398,10 @@ in
   })
 
   (mkIf cfg.dhcp-ddns.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp-ddns.settings == null) (cfg.dhcp-ddns.configFile == null);
+        message = "Either services.kea.dhcp-ddns.settings or services.kea.dhcp-ddns.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp-ddns.conf".source = dhcpDdnsConfig;
 
diff --git a/nixos/modules/services/networking/lxd-image-server.nix b/nixos/modules/services/networking/lxd-image-server.nix
index b119ba8acf6..d326626eed4 100644
--- a/nixos/modules/services/networking/lxd-image-server.nix
+++ b/nixos/modules/services/networking/lxd-image-server.nix
@@ -51,18 +51,14 @@ in
 
       environment.etc."lxd-image-server/config.toml".source = format.generate "config.toml" cfg.settings;
 
-      services.logrotate.paths.lxd-image-server = {
-        path = "/var/log/lxd-image-server/lxd-image-server.log";
+      services.logrotate.settings.lxd-image-server = {
+        files = "/var/log/lxd-image-server/lxd-image-server.log";
         frequency = "daily";
-        keep = 21;
-        extraConfig = ''
-          create 755 lxd-image-server ${cfg.group}
-          missingok
-          compress
-          delaycompress
-          copytruncate
-          notifempty
-        '';
+        rotate = 21;
+        create = "755 lxd-image-server ${cfg.group}";
+        compress = true;
+        delaycompress = true;
+        copytruncate = true;
       };
 
       systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/networking/mozillavpn.nix b/nixos/modules/services/networking/mozillavpn.nix
new file mode 100644
index 00000000000..e35ba65314e
--- /dev/null
+++ b/nixos/modules/services/networking/mozillavpn.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+{
+  options.services.mozillavpn.enable = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+    description = ''
+      Enable the Mozilla VPN daemon.
+    '';
+  };
+
+  config = lib.mkIf config.services.mozillavpn.enable {
+    environment.systemPackages = [ pkgs.mozillavpn ];
+    services.dbus.packages = [ pkgs.mozillavpn ];
+    systemd.packages = [ pkgs.mozillavpn ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ andersk ];
+}
diff --git a/nixos/modules/services/networking/nbd.nix b/nixos/modules/services/networking/nbd.nix
index 87f8c41a8e5..df3358f5187 100644
--- a/nixos/modules/services/networking/nbd.nix
+++ b/nixos/modules/services/networking/nbd.nix
@@ -4,28 +4,34 @@ with lib;
 
 let
   cfg = config.services.nbd;
-  configFormat = pkgs.formats.ini { };
   iniFields = with types; attrsOf (oneOf [ bool int float str ]);
-  serverConfig = configFormat.generate "nbd-server-config"
-    ({
-      generic =
-        (cfg.server.extraOptions // {
-          user = "root";
-          group = "root";
-          port = cfg.server.listenPort;
-        } // (optionalAttrs (cfg.server.listenAddress != null) {
-          listenaddr = cfg.server.listenAddress;
-        }));
-    }
-    // (mapAttrs
+  # The `[generic]` section must come before all the others in the
+  # config file.  This means we can't just dump an attrset to INI
+  # because that sorts the sections by name.  Instead, we serialize it
+  # on its own first.
+  genericSection = {
+    generic = (cfg.server.extraOptions // {
+      user = "root";
+      group = "root";
+      port = cfg.server.listenPort;
+    } // (optionalAttrs (cfg.server.listenAddress != null) {
+      listenaddr = cfg.server.listenAddress;
+    }));
+  };
+  exportSections =
+    mapAttrs
       (_: { path, allowAddresses, extraOptions }:
         extraOptions // {
           exportname = path;
         } // (optionalAttrs (allowAddresses != null) {
           authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses);
         }))
-      cfg.server.exports)
-    );
+      cfg.server.exports;
+  serverConfig =
+    pkgs.writeText "nbd-server-config" ''
+      ${lib.generators.toINI {} genericSection}
+      ${lib.generators.toINI {} exportSections}
+    '';
   splitLists =
     partition
       (path: hasPrefix "/dev/" path)
@@ -103,6 +109,13 @@ in
   };
 
   config = mkIf cfg.server.enable {
+    assertions = [
+      {
+        assertion = !(cfg.server.exports ? "generic");
+        message = "services.nbd.server exports must not be named 'generic'";
+      }
+    ];
+
     boot.kernelModules = [ "nbd" ];
 
     systemd.services.nbd-server = {
diff --git a/nixos/modules/services/networking/ncdns.nix b/nixos/modules/services/networking/ncdns.nix
index 82c285d0516..c8d1b6718e2 100644
--- a/nixos/modules/services/networking/ncdns.nix
+++ b/nixos/modules/services/networking/ncdns.nix
@@ -58,7 +58,7 @@ in
 
       address = mkOption {
         type = types.str;
-        default = "127.0.0.1";
+        default = "[::1]";
         description = ''
           The IP address the ncdns resolver will bind to.  Leave this unchanged
           if you do not wish to directly expose the resolver.
@@ -202,7 +202,7 @@ in
   config = mkIf cfg.enable {
 
     services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
-      forwardZonesRecurse.bit = "127.0.0.1:${toString cfg.port}";
+      forwardZonesRecurse.bit = "${cfg.address}:${toString cfg.port}";
       luaConfig =
         if cfg.dnssec.enable
           then ''readTrustAnchorsFromFile("${cfg.dnssec.keys.public}")''
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 7a9d9e5428a..242afd548df 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -5,18 +5,6 @@ with lib;
 let
   cfg = config.networking.networkmanager;
 
-  basePackages = with pkgs; [
-    modemmanager
-    networkmanager
-    networkmanager-fortisslvpn
-    networkmanager-iodine
-    networkmanager-l2tp
-    networkmanager-openconnect
-    networkmanager-openvpn
-    networkmanager-vpnc
-    networkmanager-sstp
-   ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant;
-
   delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
 
   enableIwd = cfg.wifi.backend == "iwd";
@@ -145,6 +133,15 @@ let
     '';
   };
 
+  packages = [
+    pkgs.modemmanager
+    pkgs.networkmanager
+  ]
+  ++ cfg.plugins
+  ++ lib.optionals (!delegateWireless && !enableIwd) [
+    pkgs.wpa_supplicant
+  ];
+
 in {
 
   meta = {
@@ -227,17 +224,33 @@ in {
         '';
       };
 
-      packages = mkOption {
-        type = types.listOf types.package;
+      plugins = mkOption {
+        type =
+          let
+            networkManagerPluginPackage = types.package // {
+              description = "NetworkManager plug-in";
+              check =
+                p:
+                lib.assertMsg
+                  (types.package.check p
+                    && p ? networkManagerPlugin
+                    && lib.isString p.networkManagerPlugin)
+                  ''
+                    Package ‘${p.name}’, is not a NetworkManager plug-in.
+                    Those need to have a ‘networkManagerPlugin’ attribute.
+                  '';
+            };
+          in
+          types.listOf networkManagerPluginPackage;
         default = [ ];
         description = ''
-          Extra packages that provide NetworkManager plugins.
+          List of NetworkManager plug-ins to enable.
+          Some plug-ins are enabled by the NetworkManager module by default.
         '';
-        apply = list: basePackages ++ list;
       };
 
       dhcp = mkOption {
-        type = types.enum [ "dhclient" "dhcpcd" "internal" ];
+        type = types.enum [ "dhcpcd" "internal" ];
         default = "internal";
         description = ''
           Which program (or internal library) should be used for DHCP.
@@ -380,7 +393,7 @@ in {
           </para><para>
           If you enable this option the
           <literal>networkmanager_strongswan</literal> plugin will be added to
-          the <option>networking.networkmanager.packages</option> option
+          the <option>networking.networkmanager.plugins</option> option
           so you don't need to to that yourself.
         '';
       };
@@ -399,6 +412,9 @@ in {
   };
 
   imports = [
+    (mkRenamedOptionModule
+      [ "networking" "networkmanager" "packages" ]
+      [ "networking" "networkmanager" "plugins" ])
     (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
     (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] ''
       This option was removed because allowing (multiple) regular users to
@@ -426,31 +442,12 @@ in {
 
     hardware.wirelessRegulatoryDatabase = true;
 
-    environment.etc = with pkgs; {
-      "NetworkManager/NetworkManager.conf".source = configFile;
-
-      "NetworkManager/VPN/nm-openvpn-service.name".source =
-        "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
-
-      "NetworkManager/VPN/nm-vpnc-service.name".source =
-        "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
-
-      "NetworkManager/VPN/nm-openconnect-service.name".source =
-        "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
-
-      "NetworkManager/VPN/nm-fortisslvpn-service.name".source =
-        "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
-
-      "NetworkManager/VPN/nm-l2tp-service.name".source =
-        "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
-
-      "NetworkManager/VPN/nm-iodine-service.name".source =
-        "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
-
-      "NetworkManager/VPN/nm-sstp-service.name".source =
-        "${networkmanager-sstp}/lib/NetworkManager/VPN/nm-sstp-service.name";
-
+    environment.etc = {
+        "NetworkManager/NetworkManager.conf".source = configFile;
       }
+      // builtins.listToAttrs (map (pkg: nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" {
+        source = "${pkg}/lib/NetworkManager/${pkg.networkManagerPlugin}";
+      }) cfg.plugins)
       // optionalAttrs cfg.enableFccUnlock
          {
            "ModemManager/fcc-unlock.d".source =
@@ -460,18 +457,13 @@ in {
          {
            "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
          }
-      // optionalAttrs cfg.enableStrongSwan
-         {
-           "NetworkManager/VPN/nm-strongswan-service.name".source =
-             "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
-         }
       // listToAttrs (lib.imap1 (i: s:
          {
             name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
             value = { mode = "0544"; inherit (s) source; };
          }) cfg.dispatcherScripts);
 
-    environment.systemPackages = cfg.packages;
+    environment.systemPackages = packages;
 
     users.groups = {
       networkmanager.gid = config.ids.gids.networkmanager;
@@ -490,14 +482,13 @@ in {
       };
     };
 
-    systemd.packages = cfg.packages;
+    systemd.packages = packages;
 
     systemd.tmpfiles.rules = [
       "d /etc/NetworkManager/system-connections 0700 root root -"
       "d /etc/ipsec.d 0700 root root -"
       "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"
 
-      "d /var/lib/dhclient 0755 root root -"
       "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
     ];
 
@@ -534,8 +525,20 @@ in {
         useDHCP = false;
       })
 
+      {
+        networkmanager.plugins = with pkgs; [
+          networkmanager-fortisslvpn
+          networkmanager-iodine
+          networkmanager-l2tp
+          networkmanager-openconnect
+          networkmanager-openvpn
+          networkmanager-vpnc
+          networkmanager-sstp
+        ];
+      }
+
       (mkIf cfg.enableStrongSwan {
-        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
+        networkmanager.plugins = [ pkgs.networkmanager_strongswan ];
       })
 
       (mkIf enableIwd {
@@ -559,10 +562,10 @@ in {
     security.polkit.enable = true;
     security.polkit.extraConfig = polkitConf;
 
-    services.dbus.packages = cfg.packages
+    services.dbus.packages = packages
       ++ optional cfg.enableStrongSwan pkgs.strongswanNM
       ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
 
-    services.udev.packages = cfg.packages;
+    services.udev.packages = packages;
   };
 }
diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix
new file mode 100644
index 00000000000..de4b505130e
--- /dev/null
+++ b/nixos/modules/services/networking/openconnect.nix
@@ -0,0 +1,137 @@
+{ config, lib, options, pkgs, ... }:
+with lib;
+let
+  cfg = config.networking.openconnect;
+  openconnect = cfg.package;
+  pkcs11 = types.strMatching "pkcs11:.+" // {
+    name = "pkcs11";
+    description = "PKCS#11 URI";
+  };
+  interfaceOptions = {
+    options = {
+      gateway = mkOption {
+        description = "Gateway server to connect to.";
+        example = "gateway.example.com";
+        type = types.str;
+      };
+
+      protocol = mkOption {
+        description = "Protocol to use.";
+        example = "anyconnect";
+        type =
+          types.enum [ "anyconnect" "array" "nc" "pulse" "gp" "f5" "fortinet" ];
+      };
+
+      user = mkOption {
+        description = "Username to authenticate with.";
+        example = "example-user";
+        type = types.nullOr types.str;
+      };
+
+      # Note: It does not make sense to provide a way to declaratively
+      # set an authentication cookie, because they have to be requested
+      # for every new connection and would only work once.
+      passwordFile = mkOption {
+        description = ''
+          File containing the password to authenticate with. This
+          is passed to <code>openconnect</code> via the
+          <code>--passwd-on-stdin</code> option.
+        '';
+        default = null;
+        example = "/var/lib/secrets/openconnect-passwd";
+        type = types.nullOr types.path;
+      };
+
+      certificate = mkOption {
+        description = "Certificate to authenticate with.";
+        default = null;
+        example = "/var/lib/secrets/openconnect_certificate.pem";
+        type = with types; nullOr (either path pkcs11);
+      };
+
+      privateKey = mkOption {
+        description = "Private key to authenticate with.";
+        example = "/var/lib/secrets/openconnect_private_key.pem";
+        default = null;
+        type = with types; nullOr (either path pkcs11);
+      };
+
+      extraOptions = mkOption {
+        description = ''
+          Extra config to be appended to the interface config. It should
+          contain long-format options as would be accepted on the command
+          line by <code>openconnect</code>
+          (see https://www.infradead.org/openconnect/manual.html).
+          Non-key-value options like <code>deflate</code> can be used by
+          declaring them as booleans, i. e. <code>deflate = true;</code>.
+        '';
+        default = { };
+        example = {
+          compression = "stateless";
+
+          no-http-keepalive = true;
+          no-dtls = true;
+        };
+        type = with types; attrsOf (either str bool);
+      };
+    };
+  };
+  generateExtraConfig = extra_cfg:
+    strings.concatStringsSep "\n" (attrsets.mapAttrsToList
+      (name: value: if (value == true) then name else "${name}=${value}")
+      (attrsets.filterAttrs (_: value: value != false) extra_cfg));
+  generateConfig = name: icfg:
+    pkgs.writeText "config" ''
+      interface=${name}
+      ${optionalString (icfg.user != null) "user=${icfg.user}"}
+      ${optionalString (icfg.passwordFile != null) "passwd-on-stdin"}
+      ${optionalString (icfg.certificate != null)
+      "certificate=${icfg.certificate}"}
+      ${optionalString (icfg.privateKey != null) "sslkey=${icfg.privateKey}"}
+
+      ${generateExtraConfig icfg.extraOptions}
+    '';
+  generateUnit = name: icfg: {
+    description = "OpenConnect Interface - ${name}";
+    requires = [ "network-online.target" ];
+    after = [ "network.target" "network-online.target" ];
+    wantedBy = [ "multi-user.target" ];
+
+    serviceConfig = {
+      Type = "simple";
+      ExecStart = "${openconnect}/bin/openconnect --config=${
+          generateConfig name icfg
+        } ${icfg.gateway}";
+      StandardInput = "file:${icfg.passwordFile}";
+
+      ProtectHome = true;
+    };
+  };
+in {
+  options.networking.openconnect = {
+    package = mkPackageOption pkgs "openconnect" { };
+
+    interfaces = mkOption {
+      description = "OpenConnect interfaces.";
+      default = { };
+      example = {
+        openconnect0 = {
+          gateway = "gateway.example.com";
+          protocol = "anyconnect";
+          user = "example-user";
+          passwordFile = "/var/lib/secrets/openconnect-passwd";
+        };
+      };
+      type = with types; attrsOf (submodule interfaceOptions);
+    };
+  };
+
+  config = {
+    systemd.services = mapAttrs' (name: value: {
+      name = "openconnect-${name}";
+      value = generateUnit name value;
+    }) cfg.interfaces;
+  };
+
+  meta.maintainers = with maintainers; [ alyaeanyx ];
+}
diff --git a/nixos/modules/services/networking/openfire.nix b/nixos/modules/services/networking/openfire.nix
deleted file mode 100644
index fe0499d5232..00000000000
--- a/nixos/modules/services/networking/openfire.nix
+++ /dev/null
@@ -1,56 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-  ###### interface
-
-  options = {
-
-    services.openfire = {
-
-      enable = mkEnableOption "OpenFire XMPP server";
-
-      usePostgreSQL = mkOption {
-        type = types.bool;
-        default = true;
-        description = "
-          Whether you use PostgreSQL service for your storage back-end.
-        ";
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.openfire.enable {
-
-    assertions = singleton
-      { assertion = !(config.services.openfire.usePostgreSQL -> config.services.postgresql.enable);
-        message = "OpenFire configured to use PostgreSQL but services.postgresql.enable is not enabled.";
-      };
-
-    systemd.services.openfire = {
-      description = "OpenFire XMPP server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ] ++
-        optional config.services.openfire.usePostgreSQL "postgresql.service";
-      path = with pkgs; [ jre openfire coreutils which gnugrep gawk gnused ];
-      script = ''
-        export HOME=/tmp
-        mkdir /var/log/openfire || true
-        mkdir /etc/openfire || true
-        for i in ${pkgs.openfire}/conf.inst/*; do
-            if ! test -f /etc/openfire/$(basename $i); then
-                cp $i /etc/openfire/
-            fi
-        done
-        openfire start
-      ''; # */
-    };
-  };
-
-}
diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
index 0579d314a9b..a986f83141c 100644
--- a/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -30,10 +30,10 @@ in {
     enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
 
     dns.address = mkOption {
-      type = types.str;
-      default = "0.0.0.0";
+      type = oneOrMore types.str;
+      default = [ "::" "0.0.0.0" ];
       description = ''
-        IP address Recursor DNS server will bind to.
+        IP addresses Recursor DNS server will bind to.
       '';
     };
 
@@ -47,8 +47,12 @@ in {
 
     dns.allowFrom = mkOption {
       type = types.listOf types.str;
-      default = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ];
-      example = [ "0.0.0.0/0" ];
+      default = [
+        "127.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10"
+        "169.254.0.0/16" "192.168.0.0/16" "172.16.0.0/12"
+        "::1/128" "fc00::/7" "fe80::/10"
+      ];
+      example = [ "0.0.0.0/0" "::/0" ];
       description = ''
         IP address ranges of clients allowed to make DNS queries.
       '';
@@ -72,7 +76,8 @@ in {
 
     api.allowFrom = mkOption {
       type = types.listOf types.str;
-      default = [ "0.0.0.0/0" ];
+      default = [ "127.0.0.1" "::1" ];
+      example = [ "0.0.0.0/0" "::/0" ];
       description = ''
         IP address ranges of clients allowed to make API requests.
       '';
@@ -96,7 +101,7 @@ in {
 
     forwardZonesRecurse = mkOption {
       type = types.attrs;
-      example = { eth = "127.0.0.1:5353"; };
+      example = { eth = "[::1]:5353"; };
       default = {};
       description = ''
         DNS zones to be forwarded to other recursive servers.
diff --git a/nixos/modules/services/networking/powerdns.nix b/nixos/modules/services/networking/powerdns.nix
index 8cae61b8354..b035698456c 100644
--- a/nixos/modules/services/networking/powerdns.nix
+++ b/nixos/modules/services/networking/powerdns.nix
@@ -24,14 +24,14 @@ in {
 
   config = mkIf cfg.enable {
 
-    systemd.packages = [ pkgs.powerdns ];
+    systemd.packages = [ pkgs.pdns ];
 
     systemd.services.pdns = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" "mysql.service" "postgresql.service" "openldap.service" ];
 
       serviceConfig = {
-        ExecStart = [ "" "${pkgs.powerdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
+        ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
       };
     };
 
diff --git a/nixos/modules/services/networking/shellhub-agent.nix b/nixos/modules/services/networking/shellhub-agent.nix
index a45ef148544..57825945d9f 100644
--- a/nixos/modules/services/networking/shellhub-agent.nix
+++ b/nixos/modules/services/networking/shellhub-agent.nix
@@ -1,31 +1,37 @@
 { config, lib, pkgs, ... }:
 
 with lib;
+
 let
   cfg = config.services.shellhub-agent;
-in {
-
+in
+{
   ###### interface
 
   options = {
 
     services.shellhub-agent = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
+      enable = mkEnableOption "ShellHub Agent daemon";
+
+      package = mkPackageOption pkgs "shellhub-agent" { };
+
+      preferredHostname = mkOption {
+        type = types.str;
+        default = "";
         description = ''
-          Whether to enable the ShellHub Agent daemon, which allows
-          secure remote logins.
+          Set the device preferred hostname. This provides a hint to
+          the server to use this as hostname if it is available.
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.shellhub-agent;
-        defaultText = literalExpression "pkgs.shellhub-agent";
+      keepAliveInterval = mkOption {
+        type = types.int;
+        default = 30;
         description = ''
-          Which ShellHub Agent package to use.
+          Determine the interval to send the keep alive message to
+          the server. This has a direct impact of the bandwidth
+          used by the device.
         '';
       };
 
@@ -74,9 +80,13 @@ in {
         "time-sync.target"
       ];
 
-      environment.SERVER_ADDRESS = cfg.server;
-      environment.PRIVATE_KEY = cfg.privateKey;
-      environment.TENANT_ID = cfg.tenantId;
+      environment = {
+        SHELLHUB_SERVER_ADDRESS = cfg.server;
+        SHELLHUB_PRIVATE_KEY = cfg.privateKey;
+        SHELLHUB_TENANT_ID = cfg.tenantId;
+        SHELLHUB_KEEPALIVE_INTERVAL = toString cfg.keepAliveInterval;
+        SHELLHUB_PREFERRED_HOSTNAME = cfg.preferredHostname;
+      };
 
       serviceConfig = {
         # The service starts sessions for different users.
@@ -85,7 +95,6 @@ in {
         ExecStart = "${cfg.package}/bin/agent";
       };
     };
-
-    environment.systemPackages = [ cfg.package ];
   };
 }
+
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index 4f3881af8bb..db4f0d26b6f 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -111,6 +111,13 @@ in
         description = "Whether to run squid web proxy.";
       };
 
+      package = mkOption {
+        default = pkgs.squid;
+        defaultText = literalExpression "pkgs.squid";
+        type = types.package;
+        description = "Squid package to use.";
+      };
+
       proxyAddress = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -157,17 +164,21 @@ in
     users.groups.squid = {};
 
     systemd.services.squid = {
-      description = "Squid caching web proxy";
+      description = "Squid caching proxy";
+      documentation = [ "man:squid(8)" ];
       after = [ "network.target" "nss-lookup.target" ];
       wantedBy = [ "multi-user.target"];
       preStart = ''
         mkdir -p "/var/log/squid"
         chown squid:squid "/var/log/squid"
+        ${cfg.package}/bin/squid --foreground -z -f ${squidConfig}
       '';
       serviceConfig = {
-        Type="forking";
         PIDFile="/run/squid.pid";
-        ExecStart  = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
+        ExecStart  = "${cfg.package}/bin/squid --foreground -YCs -f ${squidConfig}";
+        ExecReload="kill -HUP $MAINPID";
+        KillMode="mixed";
+        NotifyAccess="all";
       };
     };
 
diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix
index eb24130e519..8df450a11c6 100644
--- a/nixos/modules/services/networking/supplicant.nix
+++ b/nixos/modules/services/networking/supplicant.nix
@@ -43,7 +43,7 @@ let
         path = [ pkgs.coreutils ];
 
         preStart = ''
-          ${optionalString (suppl.configFile.path!=null) ''
+          ${optionalString (suppl.configFile.path!=null && suppl.configFile.writable) ''
             (umask 077 && touch -a "${suppl.configFile.path}")
           ''}
           ${optionalString suppl.userControlled.enable ''
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
index b6faf2d3f77..7694b4bf990 100644
--- a/nixos/modules/services/networking/syncplay.nix
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -61,6 +61,15 @@ in
           Group to use when running Syncplay.
         '';
       };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to the file that contains the server password. If
+          <literal>null</literal>, the server doesn't require a password.
+        '';
+      };
     };
   };
 
@@ -71,10 +80,17 @@ in
       after       = [ "network-online.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}";
         User = cfg.user;
         Group = cfg.group;
+        LoadCredential = lib.mkIf (cfg.passwordFile != null) "password:${cfg.passwordFile}";
       };
+
+      script = ''
+        ${lib.optionalString (cfg.passwordFile != null) ''
+          export SYNCPLAY_PASSWORD=$(cat "''${CREDENTIALS_DIRECTORY}/password")
+        ''}
+        exec ${pkgs.syncplay-nogui}/bin/syncplay-server ${escapeShellArgs cmdArgs}
+      '';
     };
   };
 }
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 3f41646bf01..1f64113950a 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -21,6 +21,12 @@ in {
       description = ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.'';
     };
 
+    permitCertUid = mkOption {
+      type = types.nullOr types.nonEmptyStr;
+      default = null;
+      description = "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node.";
+    };
+
     package = mkOption {
       type = types.package;
       default = pkgs.tailscale;
@@ -38,7 +44,9 @@ in {
       serviceConfig.Environment = [
         "PORT=${toString cfg.port}"
         ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"''
-      ];
+      ] ++ (lib.optionals (cfg.permitCertUid != null) [
+        "TS_PERMIT_CERT_UID=${cfg.permitCertUid}"
+      ]);
     };
   };
 }
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 414775fc357..61e9fe5096b 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -17,6 +17,13 @@ let
         description = "The IP addresses of the interface.";
       };
 
+      autostart = mkOption {
+        description = "Whether to bring up this interface automatically during boot.";
+        default = true;
+        example = false;
+        type = types.bool;
+      };
+
       dns = mkOption {
         example = [ "192.168.2.2" ];
         default = [];
@@ -247,7 +254,7 @@ let
         description = "wg-quick WireGuard Tunnel - ${name}";
         requires = [ "network-online.target" ];
         after = [ "network.target" "network-online.target" ];
-        wantedBy = [ "multi-user.target" ];
+        wantedBy = optional values.autostart "multi-user.target";
         environment.DEVICE = name;
         path = [ pkgs.kmod pkgs.wireguard-tools ];
 
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 3370390a4c6..dd83b7facc1 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -90,5 +90,5 @@ in with lib; {
     (mkRemovedOptionModule [ "services" "zeronet" "logDir" ] "Zeronet will log by default in /var/lib/zeronet")
   ];
 
-  meta.maintainers = with maintainers; [ chiiruno ];
+  meta.maintainers = with maintainers; [ Madouura ];
 }
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 4d356242417..5c89d587237 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -102,17 +102,19 @@ in
     # Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go
     provider = mkOption {
       type = types.enum [
-        "google"
+        "adfs"
         "azure"
+        "bitbucket"
+        "digitalocean"
         "facebook"
         "github"
-        "keycloak"
         "gitlab"
+        "google"
+        "keycloak"
+        "keycloak-oidc"
         "linkedin"
         "login.gov"
-        "bitbucket"
         "nextcloud"
-        "digitalocean"
         "oidc"
       ];
       default = "google";
@@ -569,8 +571,11 @@ in
     users.users.oauth2_proxy = {
       description = "OAuth2 Proxy";
       isSystemUser = true;
+      group = "oauth2_proxy";
     };
 
+    users.groups.oauth2_proxy = {};
+
     systemd.services.oauth2_proxy = {
       description = "OAuth2 Proxy";
       path = [ cfg.package ];
diff --git a/nixos/modules/services/security/sslmate-agent.nix b/nixos/modules/services/security/sslmate-agent.nix
new file mode 100644
index 00000000000..c850eb22a03
--- /dev/null
+++ b/nixos/modules/services/security/sslmate-agent.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sslmate-agent;
+
+in {
+  meta.maintainers = with maintainers; [ wolfangaukang ];
+
+  options = {
+    services.sslmate-agent = {
+      enable = mkEnableOption "sslmate-agent, a daemon for managing SSL/TLS certificates on a server";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ sslmate-agent ];
+
+    systemd = {
+      packages = [ pkgs.sslmate-agent ];
+      services.sslmate-agent = {
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ConfigurationDirectory = "sslmate-agent";
+          LogsDirectory = "sslmate-agent";
+          StateDirectory = "sslmate-agent";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index ddd216ca7fd..a5822c02794 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -910,6 +910,11 @@ in
         ORPort = mkForce [];
         PublishServerDescriptor = mkForce false;
       })
+      (mkIf (!cfg.client.enable) {
+        # Make sure application connections via SOCKS are disabled
+        # when services.tor.client.enable is false
+        SOCKSPort = mkForce [ 0 ];
+      })
       (mkIf cfg.client.enable (
         { SOCKSPort = [ cfg.client.socksListenAddress ];
         } // optionalAttrs cfg.client.transparentProxy.enable {
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index ddd5bcebcdd..62935855989 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -5,8 +5,8 @@ let
 
   inherit (lib)
     mkDefault mkEnableOption mkIf mkOption types
-    mkRemovedOptionModule
-    concatStringsSep optional;
+    mkRemovedOptionModule literalExpression
+    escapeShellArg concatStringsSep optional optionalString;
 
 in
 {
@@ -17,10 +17,26 @@ in
       type = types.ints.between 1 100;
       default = 10;
       description = ''
-        Minimum of availabe memory (in percent).
-        If the free memory falls below this threshold and the analog is true for
-        <option>services.earlyoom.freeSwapThreshold</option>
-        the killing begins.
+        Minimum available memory (in percent).
+
+        If the available memory falls below this threshold (and the analog is true for
+        <option>freeSwapThreshold</option>) the killing begins.
+        SIGTERM is sent first to the process that uses the most memory; then, if the available
+        memory falls below <option>freeMemKillThreshold</option> (and the analog is true for
+        <option>freeSwapKillThreshold</option>), SIGKILL is sent.
+
+        See <link xlink:href="https://github.com/rfjakob/earlyoom#command-line-options">README</link> for details.
+      '';
+    };
+
+    freeMemKillThreshold = mkOption {
+      type = types.nullOr (types.ints.between 1 100);
+      default = null;
+      description = ''
+        Minimum available memory (in percent) before sending SIGKILL.
+        If unset, this defaults to half of <option>freeMemThreshold</option>.
+
+        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
       '';
     };
 
@@ -28,19 +44,20 @@ in
       type = types.ints.between 1 100;
       default = 10;
       description = ''
-        Minimum of availabe swap space (in percent).
-        If the available swap space falls below this threshold and the analog
-        is true for <option>services.earlyoom.freeMemThreshold</option>
-        the killing begins.
+        Minimum free swap space (in percent) before sending SIGTERM.
+
+        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
       '';
     };
 
-    # TODO: remove or warn after 1.7 (https://github.com/rfjakob/earlyoom/commit/7ebc4554)
-    ignoreOOMScoreAdjust = mkOption {
-      type = types.bool;
-      default = false;
+    freeSwapKillThreshold = mkOption {
+      type = types.nullOr (types.ints.between 1 100);
+      default = null;
       description = ''
-        Ignore oom_score_adjust values of processes.
+        Minimum free swap space (in percent) before sending SIGKILL.
+        If unset, this defaults to half of <option>freeSwapThreshold</option>.
+
+        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
       '';
     };
 
@@ -63,12 +80,43 @@ in
         local user to DoS your session by spamming notifications.
 
         To actually see the notifications in your GUI session, you need to have
-        <literal>systembus-notify</literal> running as your user which this
-        option handles.
+        <literal>systembus-notify</literal> running as your user, which this
+        option handles by enabling <option>services.systembus-notify</option>.
 
         See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
       '';
     };
+
+    killHook = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = literalExpression ''
+        pkgs.writeShellScript "earlyoom-kill-hook" '''
+          echo "Process $EARLYOOM_NAME ($EARLYOOM_PID) was killed" >> /path/to/log
+        '''
+      '';
+      description = ''
+        An absolute path to an executable to be run for each process killed.
+        Some environment variables are available, see
+        <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> and
+        <link xlink:href="https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-n-pathtoscript">the man page</link>
+        for details.
+      '';
+    };
+
+    reportInterval = mkOption {
+      type = types.int;
+      default = 3600;
+      example = 0;
+      description = "Interval (in seconds) at which a memory report is printed (set to 0 to disable).";
+    };
+
+    extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "-g" "--prefer '(^|/)(java|chromium)$'" ];
+      description = "Extra command-line arguments to be passed to earlyoom.";
+    };
   };
 
   imports = [
@@ -76,7 +124,11 @@ in
       This option is deprecated and ignored by earlyoom since 1.2.
     '')
     (mkRemovedOptionModule [ "services" "earlyoom" "notificationsCommand" ] ''
-      This option is deprecated and ignored by earlyoom since 1.6.
+      This option was removed in earlyoom 1.6, but was reimplemented in 1.7
+      and is available as the new option `services.earlyoom.killHook`.
+    '')
+    (mkRemovedOptionModule [ "services" "earlyoom" "ignoreOOMScoreAdjust" ] ''
+      This option is deprecated and ignored by earlyoom since 1.7.
     '')
   ];
 
@@ -91,12 +143,16 @@ in
         StandardError = "journal";
         ExecStart = concatStringsSep " " ([
           "${pkgs.earlyoom}/bin/earlyoom"
-          "-m ${toString cfg.freeMemThreshold}"
-          "-s ${toString cfg.freeSwapThreshold}"
+          ("-m ${toString cfg.freeMemThreshold}"
+            + optionalString (cfg.freeMemKillThreshold != null) ",${toString cfg.freeMemKillThreshold}")
+          ("-s ${toString cfg.freeSwapThreshold}"
+            + optionalString (cfg.freeSwapKillThreshold != null) ",${toString cfg.freeSwapKillThreshold}")
+          "-r ${toString cfg.reportInterval}"
         ]
-        ++ optional cfg.ignoreOOMScoreAdjust "-i"
         ++ optional cfg.enableDebugInfo "-d"
         ++ optional cfg.enableNotifications "-n"
+        ++ optional (cfg.killHook != null) "-N ${escapeShellArg cfg.killHook}"
+        ++ cfg.extraArgs
         );
       };
     };
diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix
index 00a87e788dc..0caebc8ce90 100644
--- a/nixos/modules/services/system/nscd.nix
+++ b/nixos/modules/services/system/nscd.nix
@@ -7,10 +7,6 @@ let
   nssModulesPath = config.system.nssModules.path;
   cfg = config.services.nscd;
 
-  nscd = if pkgs.stdenv.hostPlatform.libc == "glibc"
-         then pkgs.stdenv.cc.libc.bin
-         else pkgs.glibc.bin;
-
 in
 
 {
@@ -37,6 +33,19 @@ in
         description = "Configuration to use for Name Service Cache Daemon.";
       };
 
+      package = mkOption {
+        type = types.package;
+        default = if pkgs.stdenv.hostPlatform.libc == "glibc"
+          then pkgs.stdenv.cc.libc.bin
+          else pkgs.glibc.bin;
+        defaultText = literalExample ''
+          if pkgs.stdenv.hostPlatform.libc == "glibc"
+            then pkgs.stdenv.cc.libc.bin
+            else pkgs.glibc.bin;
+        '';
+        description = "package containing the nscd binary to be used by the service";
+      };
+
     };
 
   };
@@ -69,16 +78,16 @@ in
         # files. So prefix the ExecStart command with "!" to prevent systemd
         # from dropping privileges early. See ExecStart in systemd.service(5).
         serviceConfig =
-          { ExecStart = "!@${nscd}/sbin/nscd nscd";
+          { ExecStart = "!@${cfg.package}/bin/nscd nscd";
             Type = "forking";
             DynamicUser = true;
             RuntimeDirectory = "nscd";
             PIDFile = "/run/nscd/nscd.pid";
             Restart = "always";
             ExecReload =
-              [ "${nscd}/sbin/nscd --invalidate passwd"
-                "${nscd}/sbin/nscd --invalidate group"
-                "${nscd}/sbin/nscd --invalidate hosts"
+              [ "${cfg.package}/bin/nscd --invalidate passwd"
+                "${cfg.package}/bin/nscd --invalidate group"
+                "${cfg.package}/bin/nscd --invalidate hosts"
               ];
           };
       };
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 4fe720bf044..e02ab3cb6b3 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -1,6 +1,6 @@
 { config, pkgs, lib, ... }:
 let
-  inherit (lib) mkOption types mkIf;
+  inherit (lib) mapAttrs mkIf mkOption optional optionals types;
 
   cfg = config.services.kmscon;
 
@@ -28,6 +28,19 @@ in {
         default = false;
       };
 
+      fonts = mkOption {
+        description = "Fonts used by kmscon, in order of priority.";
+        default = null;
+        example = lib.literalExpression ''[ { name = "Source Code Pro"; package = pkgs.source-code-pro; } ]'';
+        type = with types;
+          let fontType = submodule {
+                options = {
+                  name = mkOption { type = str; description = "Font name, as used by fontconfig."; };
+                  package = mkOption { type = package; description = "Package providing the font."; };
+                };
+          }; in nullOr (nonEmptyListOf fontType);
+      };
+
       extraConfig = mkOption {
         description = "Extra contents of the kmscon.conf file.";
         type = types.lines;
@@ -87,11 +100,17 @@ in {
 
     systemd.services.systemd-vconsole-setup.enable = false;
 
-    services.kmscon.extraConfig = mkIf cfg.hwRender ''
-      drm
-      hwaccel
-    '';
+    services.kmscon.extraConfig =
+      let
+        render = optionals cfg.hwRender [ "drm" "hwaccel" ];
+        fonts = optional (cfg.fonts != null) "font-name=${lib.concatMapStringsSep ", " (f: f.name) cfg.fonts}";
+      in lib.concatStringsSep "\n" (render ++ fonts);
 
     hardware.opengl.enable = mkIf cfg.hwRender true;
+
+    fonts = mkIf (cfg.fonts != null) {
+      fontconfig.enable = true;
+      fonts = map (f: f.package) cfg.fonts;
+    };
   };
 }
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index 43208a9fe4c..11d9fe30547 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -16,7 +16,7 @@ let
     -pidfile ${cfg.pidFile} \
     -procname unifi-video \
     -Djava.security.egd=file:/dev/./urandom \
-    -Xmx${cfg.maximumJavaHeapSize}M \
+    -Xmx${toString cfg.maximumJavaHeapSize}M \
     -Xss512K \
     -XX:+UseG1GC \
     -XX:+UseStringDeduplication \
@@ -91,98 +91,102 @@ let
   stateDir = "/var/lib/unifi-video";
 
 in
-  {
-
-    options.services.unifi-video = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether or not to enable the unifi-video service.
-        '';
-      };
+{
 
-      jrePackage = mkOption {
-        type = types.package;
-        default = pkgs.jre8;
-        defaultText = literalExpression "pkgs.jre8";
-        description = ''
-          The JRE package to use. Check the release notes to ensure it is supported.
-        '';
-      };
+  options.services.unifi-video = {
 
-      unifiVideoPackage = mkOption {
-        type = types.package;
-        default = pkgs.unifi-video;
-        defaultText = literalExpression "pkgs.unifi-video";
-        description = ''
-          The unifi-video package to use.
-        '';
-      };
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether or not to enable the unifi-video service.
+      '';
+    };
 
-      mongodbPackage = mkOption {
-        type = types.package;
-        default = pkgs.mongodb-4_0;
-        defaultText = literalExpression "pkgs.mongodb";
-        description = ''
-          The mongodb package to use.
-        '';
-      };
+    jrePackage = mkOption {
+      type = types.package;
+      default = pkgs.jre8;
+      defaultText = literalExpression "pkgs.jre8";
+      description = ''
+        The JRE package to use. Check the release notes to ensure it is supported.
+      '';
+    };
 
-      logDir = mkOption {
-        type = types.str;
-        default = "${stateDir}/logs";
-        description = ''
-          Where to store the logs.
-        '';
-      };
+    unifiVideoPackage = mkOption {
+      type = types.package;
+      default = pkgs.unifi-video;
+      defaultText = literalExpression "pkgs.unifi-video";
+      description = ''
+        The unifi-video package to use.
+      '';
+    };
 
-      dataDir = mkOption {
-        type = types.str;
-        default = "${stateDir}/data";
-        description = ''
-          Where to store the database and other data.
-        '';
-      };
+    mongodbPackage = mkOption {
+      type = types.package;
+      default = pkgs.mongodb-4_0;
+      defaultText = literalExpression "pkgs.mongodb";
+      description = ''
+        The mongodb package to use.
+      '';
+    };
 
-      openPorts = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether or not to open the required ports on the firewall.
-        '';
-      };
+    logDir = mkOption {
+      type = types.str;
+      default = "${stateDir}/logs";
+      description = ''
+        Where to store the logs.
+      '';
+    };
 
-      maximumJavaHeapSize = mkOption {
-        type = types.nullOr types.int;
-        default = 1024;
-        example = 4096;
-        description = ''
-          Set the maximimum heap size for the JVM in MB.
-        '';
-      };
+    dataDir = mkOption {
+      type = types.str;
+      default = "${stateDir}/data";
+      description = ''
+        Where to store the database and other data.
+      '';
+    };
 
-      pidFile = mkOption {
-        type = types.path;
-        default = "${cfg.dataDir}/unifi-video.pid";
-        defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
-        description = "Location of unifi-video pid file.";
-      };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not to open the required ports on the firewall.
+      '';
+    };
+
+    maximumJavaHeapSize = mkOption {
+      type = types.nullOr types.int;
+      default = 1024;
+      example = 4096;
+      description = ''
+        Set the maximimum heap size for the JVM in MB.
+      '';
+    };
+
+    pidFile = mkOption {
+      type = types.path;
+      default = "${cfg.dataDir}/unifi-video.pid";
+      defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
+      description = "Location of unifi-video pid file.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
 
-};
+    warnings = optional
+      (options.services.unifi-video.openFirewall.highestPrio >= (mkOptionDefault null).priority)
+      "The current services.unifi-video.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";
 
-config = mkIf cfg.enable {
-  users = {
-    users.unifi-video = {
+    users.users.unifi-video = {
       description = "UniFi Video controller daemon user";
       home = stateDir;
       group = "unifi-video";
       isSystemUser = true;
     };
-    groups.unifi-video = {};
-  };
+    users.groups.unifi-video = {};
 
-  networking.firewall = mkIf cfg.openPorts {
+    networking.firewall = mkIf cfg.openFirewall {
       # https://help.ui.com/hc/en-us/articles/217875218-UniFi-Video-Ports-Used
       allowedTCPPorts = [
         7080 # HTTP portal
@@ -237,7 +241,6 @@ config = mkIf cfg.enable {
       "L+ '${stateDir}/conf/server.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/server.xml"
       "L+ '${stateDir}/conf/tomcat-users.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/tomcat-users.xml"
       "L+ '${stateDir}/conf/web.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/web.xml"
-
     ];
 
     systemd.services.unifi-video = {
@@ -258,10 +261,11 @@ config = mkIf cfg.enable {
         WorkingDirectory = "${stateDir}";
       };
     };
-
   };
 
-  meta = {
-    maintainers = with lib.maintainers; [ rsynnest ];
-  };
+  imports = [
+    (mkRenamedOptionModule [ "services" "unifi-video" "openPorts" ] [ "services" "unifi-video" "openFirewall" ])
+  ];
+
+  meta.maintainers = with lib.maintainers; [ rsynnest ];
 }
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index d7a26838d6f..a120f6cdb3d 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -151,6 +151,7 @@ in
     users.users.${cfg.user} = {
       isSystemUser = true;
       group = cfg.group;
+      home = cfg.home;
     };
 
     users.groups.${cfg.group} = {};
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index 2c2911aada3..7dbbf4a12fe 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -609,6 +609,7 @@ in
       connection_reaper_interval = 30;
       relative_url_root = null;
       message_bus_max_backlog_size = 100;
+      message_bus_clear_every = 50;
       secret_key_base = cfg.secretKeyBaseFile;
       fallback_assets_path = null;
 
@@ -655,7 +656,12 @@ in
       long_polling_interval = null;
     };
 
-    services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost");
+    services.redis.servers.discourse =
+      lib.mkIf (lib.elem cfg.redis.host [ "localhost" "127.0.0.1" ]) {
+        enable = true;
+        bind = cfg.redis.host;
+        port = cfg.backendSettings.redis_port;
+      };
 
     services.postgresql = lib.mkIf databaseActuallyCreateLocally {
       enable = true;
@@ -696,12 +702,12 @@ in
     systemd.services.discourse = {
       wantedBy = [ "multi-user.target" ];
       after = [
-        "redis.service"
+        "redis-discourse.service"
         "postgresql.service"
         "discourse-postgresql.service"
       ];
       bindsTo = [
-        "redis.service"
+        "redis-discourse.service"
       ] ++ lib.optionals (cfg.database.host == null) [
         "postgresql.service"
         "discourse-postgresql.service"
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 2f1c4acec1e..be0b5b94fb2 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -159,7 +159,7 @@ in
       '';
     };
 
-    caddy.enable = mkEnableOption "Whether to enablle caddy reverse proxy to expose jitsi-meet";
+    caddy.enable = mkEnableOption "Whether to enable caddy reverse proxy to expose jitsi-meet";
 
     prosody.enable = mkOption {
       type = bool;
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index 22c16be7613..2d817ca1923 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -4,20 +4,94 @@ let
   cfg = config.services.keycloak;
   opt = options.services.keycloak;
 
-  inherit (lib) types mkOption concatStringsSep mapAttrsToList
-    escapeShellArg recursiveUpdate optionalAttrs boolToString mkOrder
-    sort filterAttrs concatMapStringsSep concatStrings mkIf
-    optionalString optionals mkDefault literalExpression hasSuffix
-    foldl' isAttrs filter attrNames elem literalDocBook
-    maintainers;
-
-  inherit (builtins) match typeOf;
+  inherit (lib)
+    types
+    mkMerge
+    mkOption
+    mkChangedOptionModule
+    mkRenamedOptionModule
+    mkRemovedOptionModule
+    concatStringsSep
+    mapAttrsToList
+    escapeShellArg
+    mkIf
+    optionalString
+    optionals
+    mkDefault
+    literalExpression
+    isAttrs
+    literalDocBook
+    maintainers
+    catAttrs
+    collect
+    splitString
+    ;
+
+  inherit (builtins)
+    elem
+    typeOf
+    isInt
+    isString
+    hashString
+    isPath
+    ;
+
+  prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}";
 in
 {
+  imports =
+    [
+      (mkRenamedOptionModule
+        [ "services" "keycloak" "bindAddress" ]
+        [ "services" "keycloak" "settings" "http-host" ])
+      (mkRenamedOptionModule
+        [ "services" "keycloak" "forceBackendUrlToFrontendUrl"]
+        [ "services" "keycloak" "settings" "hostname-strict-backchannel"])
+      (mkChangedOptionModule
+        [ "services" "keycloak" "httpPort" ]
+        [ "services" "keycloak" "settings" "http-port" ]
+        (config:
+          builtins.fromJSON config.services.keycloak.httpPort))
+      (mkChangedOptionModule
+        [ "services" "keycloak" "httpsPort" ]
+        [ "services" "keycloak" "settings" "https-port" ]
+        (config:
+          builtins.fromJSON config.services.keycloak.httpsPort))
+      (mkRemovedOptionModule
+        [ "services" "keycloak" "frontendUrl" ]
+        ''
+          Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead.
+          NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients.
+                See its description for more information.
+        '')
+      (mkRemovedOptionModule
+        [ "services" "keycloak" "extraConfig" ]
+        "Use `services.keycloak.settings' instead.")
+    ];
+
   options.services.keycloak =
     let
-      inherit (types) bool str nullOr attrsOf path enum anything
-        package port;
+      inherit (types)
+        bool
+        str
+        int
+        nullOr
+        attrsOf
+        oneOf
+        path
+        enum
+        package
+        port;
+
+      assertStringPath = optionName: value:
+        if isPath value then
+          throw ''
+            services.keycloak.${optionName}:
+              ${toString value}
+              is a Nix path, but should be a string, since Nix
+              paths are copied into the world-readable Nix store.
+          ''
+        else value;
     in
     {
       enable = mkOption {
@@ -30,89 +104,14 @@ in
         '';
       };
 
-      bindAddress = mkOption {
-        type = str;
-        default = "\${jboss.bind.address:0.0.0.0}";
-        example = "127.0.0.1";
-        description = ''
-          On which address Keycloak should accept new connections.
-
-          A special syntax can be used to allow command line Java system
-          properties to override the value: ''${property.name:value}
-        '';
-      };
-
-      httpPort = mkOption {
-        type = str;
-        default = "\${jboss.http.port:80}";
-        example = "8080";
-        description = ''
-          On which port Keycloak should listen for new HTTP connections.
-
-          A special syntax can be used to allow command line Java system
-          properties to override the value: ''${property.name:value}
-        '';
-      };
-
-      httpsPort = mkOption {
-        type = str;
-        default = "\${jboss.https.port:443}";
-        example = "8443";
-        description = ''
-          On which port Keycloak should listen for new HTTPS connections.
-
-          A special syntax can be used to allow command line Java system
-          properties to override the value: ''${property.name:value}
-        '';
-      };
-
-      frontendUrl = mkOption {
-        type = str;
-        apply = x:
-          if x == "" || hasSuffix "/" x then
-            x
-          else
-            x + "/";
-        example = "keycloak.example.com/auth";
-        description = ''
-          The public URL used as base for all frontend requests. Should
-          normally include a trailing <literal>/auth</literal>.
-
-          See <link xlink:href="https://www.keycloak.org/docs/latest/server_installation/#_hostname">the
-          Hostname section of the Keycloak server installation
-          manual</link> for more information.
-        '';
-      };
-
-      forceBackendUrlToFrontendUrl = mkOption {
-        type = bool;
-        default = false;
-        example = true;
-        description = ''
-          Whether Keycloak should force all requests to go through the
-          frontend URL configured in <xref
-          linkend="opt-services.keycloak.frontendUrl" />. By default,
-          Keycloak allows backend requests to instead use its local
-          hostname or IP address and may also advertise it to clients
-          through its OpenID Connect Discovery endpoint.
-
-          See <link
-          xlink:href="https://www.keycloak.org/docs/latest/server_installation/#_hostname">the
-          Hostname section of the Keycloak server installation
-          manual</link> for more information.
-        '';
-      };
-
       sslCertificate = mkOption {
         type = nullOr path;
         default = null;
         example = "/run/keys/ssl_cert";
+        apply = assertStringPath "sslCertificate";
         description = ''
           The path to a PEM formatted certificate to use for TLS/SSL
           connections.
-
-          This should be a string, not a Nix path, since Nix paths are
-          copied into the world-readable Nix store.
         '';
       };
 
@@ -120,20 +119,28 @@ in
         type = nullOr path;
         default = null;
         example = "/run/keys/ssl_key";
+        apply = assertStringPath "sslCertificateKey";
         description = ''
           The path to a PEM formatted private key to use for TLS/SSL
           connections.
+        '';
+      };
 
-          This should be a string, not a Nix path, since Nix paths are
-          copied into the world-readable Nix store.
+      plugins = lib.mkOption {
+        type = lib.types.listOf lib.types.path;
+        default = [ ];
+        description = ''
+          Keycloak plugin jar, ear files or derivations containing
+          them. Packaged plugins are available through
+          <literal>pkgs.keycloak.plugins</literal>.
         '';
       };
 
       database = {
         type = mkOption {
-          type = enum [ "mysql" "postgresql" ];
+          type = enum [ "mysql" "mariadb" "postgresql" ];
           default = "postgresql";
-          example = "mysql";
+          example = "mariadb";
           description = ''
             The type of database Keycloak should connect to.
           '';
@@ -151,6 +158,7 @@ in
           let
             dbPorts = {
               postgresql = 5432;
+              mariadb = 3306;
               mysql = 3306;
             };
           in
@@ -199,6 +207,21 @@ in
           '';
         };
 
+        name = mkOption {
+          type = str;
+          default = "keycloak";
+          description = ''
+            Database name to use when connecting to an external or
+            manually provisioned database; has no effect when a local
+            database is automatically provisioned.
+
+            To use this with a local database, set <xref
+            linkend="opt-services.keycloak.database.createLocally" /> to
+            <literal>false</literal> and create the database and user
+            manually.
+          '';
+        };
+
         username = mkOption {
           type = str;
           default = "keycloak";
@@ -210,19 +233,16 @@ in
             To use this with a local database, set <xref
             linkend="opt-services.keycloak.database.createLocally" /> to
             <literal>false</literal> and create the database and user
-            manually. The database should be called
-            <literal>keycloak</literal>.
+            manually.
           '';
         };
 
         passwordFile = mkOption {
           type = path;
           example = "/run/keys/db_password";
+          apply = assertStringPath "passwordFile";
           description = ''
-            File containing the database password.
-
-            This should be a string, not a Nix path, since Nix paths are
-            copied into the world-readable Nix store.
+            The path to a file containing the database password.
           '';
         };
       };
@@ -260,67 +280,181 @@ in
         '';
       };
 
-      extraConfig = mkOption {
-        type = attrsOf anything;
-        default = { };
+      settings = mkOption {
+        type = lib.types.submodule {
+          freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ]));
+
+          options = {
+            http-host = mkOption {
+              type = str;
+              default = "0.0.0.0";
+              example = "127.0.0.1";
+              description = ''
+                On which address Keycloak should accept new connections.
+              '';
+            };
+
+            http-port = mkOption {
+              type = port;
+              default = 80;
+              example = 8080;
+              description = ''
+                On which port Keycloak should listen for new HTTP connections.
+              '';
+            };
+
+            https-port = mkOption {
+              type = port;
+              default = 443;
+              example = 8443;
+              description = ''
+                On which port Keycloak should listen for new HTTPS connections.
+              '';
+            };
+
+            http-relative-path = mkOption {
+              type = str;
+              default = "";
+              example = "/auth";
+              description = ''
+                The path relative to <literal>/</literal> for serving
+                resources.
+
+                <note>
+                  <para>
+                    In versions of Keycloak using Wildfly (&lt;17),
+                    this defaulted to <literal>/auth</literal>. If
+                    upgrading from the Wildfly version of Keycloak,
+                    i.e. a NixOS version before 22.05, you'll likely
+                    want to set this to <literal>/auth</literal> to
+                    keep compatibility with your clients.
+
+                    See <link
+                    xlink:href="https://www.keycloak.org/migration/migrating-to-quarkus"
+                    /> for more information on migrating from Wildfly
+                    to Quarkus.
+                  </para>
+                </note>
+              '';
+            };
+
+            hostname = mkOption {
+              type = str;
+              example = "keycloak.example.com";
+              description = ''
+                The hostname part of the public URL used as base for
+                all frontend requests.
+
+                See <link xlink:href="https://www.keycloak.org/server/hostname" />
+                for more information about hostname configuration.
+              '';
+            };
+
+            hostname-strict-backchannel = mkOption {
+              type = bool;
+              default = false;
+              example = true;
+              description = ''
+                Whether Keycloak should force all requests to go
+                through the frontend URL. By default, Keycloak allows
+                backend requests to instead use its local hostname or
+                IP address and may also advertise it to clients
+                through its OpenID Connect Discovery endpoint.
+
+                See <link xlink:href="https://www.keycloak.org/server/hostname" />
+                for more information about hostname configuration.
+              '';
+            };
+
+            proxy = mkOption {
+              type = enum [ "edge" "reencrypt" "passthrough" "none" ];
+              default = "none";
+              example = "edge";
+              description = ''
+                The proxy address forwarding mode if the server is
+                behind a reverse proxy.
+
+                <variablelist>
+                  <varlistentry>
+                    <term>edge</term>
+                    <listitem>
+                      <para>
+                        Enables communication through HTTP between the
+                        proxy and Keycloak.
+                      </para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term>reencrypt</term>
+                    <listitem>
+                      <para>
+                        Requires communication through HTTPS between the
+                        proxy and Keycloak.
+                      </para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term>passthrough</term>
+                    <listitem>
+                      <para>
+                        Enables communication through HTTP or HTTPS between
+                        the proxy and Keycloak.
+                      </para>
+                    </listitem>
+                  </varlistentry>
+                </variablelist>
+
+                See <link
+                xlink:href="https://www.keycloak.org/server/reverseproxy"
+                /> for more information.
+              '';
+            };
+          };
+        };
+
         example = literalExpression ''
           {
-            "subsystem=keycloak-server" = {
-              "spi=hostname" = {
-                "provider=default" = null;
-                "provider=fixed" = {
-                  enabled = true;
-                  properties.hostname = "keycloak.example.com";
-                };
-                default-provider = "fixed";
-              };
-            };
+            hostname = "keycloak.example.com";
+            proxy = "reencrypt";
+            https-key-store-file = "/path/to/file";
+            https-key-store-password = { _secret = "/run/keys/store_password"; };
           }
         '';
+
         description = ''
-          Additional Keycloak configuration options to set in
-          <literal>standalone.xml</literal>.
-
-          Options are expressed as a Nix attribute set which matches the
-          structure of the jboss-cli configuration. The configuration is
-          effectively overlayed on top of the default configuration
-          shipped with Keycloak. To remove existing nodes and undefine
-          attributes from the default configuration, set them to
-          <literal>null</literal>.
-
-          The example configuration does the equivalent of the following
-          script, which removes the hostname provider
-          <literal>default</literal>, adds the deprecated hostname
-          provider <literal>fixed</literal> and defines it the default:
-
-          <programlisting>
-          /subsystem=keycloak-server/spi=hostname/provider=default:remove()
-          /subsystem=keycloak-server/spi=hostname/provider=fixed:add(enabled = true, properties = { hostname = "keycloak.example.com" })
-          /subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="fixed")
-          </programlisting>
-
-          You can discover available options by using the <link
-          xlink:href="http://docs.wildfly.org/21/Admin_Guide.html#Command_Line_Interface">jboss-cli.sh</link>
-          program and by referring to the <link
-          xlink:href="https://www.keycloak.org/docs/latest/server_installation/index.html">Keycloak
-          Server Installation and Configuration Guide</link>.
+          Configuration options corresponding to parameters set in
+          <filename>conf/keycloak.conf</filename>.
+
+          Most available options are documented at <link
+          xlink:href="https://www.keycloak.org/server/all-config" />.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute <literal>_secret</literal> - a
+          string pointing to a file containing the value the option
+          should be set to. See the example to get a better picture of
+          this: in the resulting
+          <filename>conf/keycloak.conf</filename> file, the
+          <literal>https-key-store-password</literal> key will be set
+          to the contents of the
+          <filename>/run/keys/store_password</filename> file.
         '';
       };
-
     };
 
   config =
     let
-      # We only want to create a database if we're actually going to connect to it.
+      # We only want to create a database if we're actually going to
+      # connect to it.
       databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost";
       createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql";
-      createLocalMySQL = databaseActuallyCreateLocally && cfg.database.type == "mysql";
+      createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ];
 
       mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } ''
         ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
       '';
 
-      # Both theme and theme type directories need to be actual directories in one hierarchy to pass Keycloak checks.
+      # Both theme and theme type directories need to be actual
+      # directories in one hierarchy to pass Keycloak checks.
       themesBundle = pkgs.runCommand "keycloak-themes" { } ''
         linkTheme() {
           theme="$1"
@@ -339,7 +473,7 @@ in
         }
 
         mkdir -p "$out"
-        for theme in ${cfg.package}/themes/*; do
+        for theme in ${keycloakBuild}/themes/*; do
           if [ -d "$theme" ]; then
             linkTheme "$theme" "$(basename "$theme")"
           fi
@@ -348,329 +482,25 @@ in
         ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)}
       '';
 
-      keycloakConfig' = foldl' recursiveUpdate
-        {
-          "interface=public".inet-address = cfg.bindAddress;
-          "socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort;
-          "subsystem=keycloak-server" = {
-            "spi=hostname"."provider=default" = {
-              enabled = true;
-              properties = {
-                inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl;
-              };
-            };
-            "theme=defaults".dir = toString themesBundle;
-          };
-          "subsystem=datasources"."data-source=KeycloakDS" = {
-            max-pool-size = "20";
-            user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
-            password = "@db-password@";
-          };
-        } [
-        (optionalAttrs (cfg.database.type == "postgresql") {
-          "subsystem=datasources" = {
-            "jdbc-driver=postgresql" = {
-              driver-module-name = "org.postgresql";
-              driver-name = "postgresql";
-              driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
-            };
-            "data-source=KeycloakDS" = {
-              connection-url = "jdbc:postgresql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
-              driver-name = "postgresql";
-              "connection-properties=ssl".value = boolToString cfg.database.useSSL;
-            } // (optionalAttrs (cfg.database.caCert != null) {
-              "connection-properties=sslrootcert".value = cfg.database.caCert;
-              "connection-properties=sslmode".value = "verify-ca";
-            });
-          };
-        })
-        (optionalAttrs (cfg.database.type == "mysql") {
-          "subsystem=datasources" = {
-            "jdbc-driver=mysql" = {
-              driver-module-name = "com.mysql";
-              driver-name = "mysql";
-              driver-class-name = "com.mysql.jdbc.Driver";
-            };
-            "data-source=KeycloakDS" = {
-              connection-url = "jdbc:mysql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
-              driver-name = "mysql";
-              "connection-properties=useSSL".value = boolToString cfg.database.useSSL;
-              "connection-properties=requireSSL".value = boolToString cfg.database.useSSL;
-              "connection-properties=verifyServerCertificate".value = boolToString cfg.database.useSSL;
-              "connection-properties=characterEncoding".value = "UTF-8";
-              valid-connection-checker-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker";
-              validate-on-match = true;
-              exception-sorter-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter";
-            } // (optionalAttrs (cfg.database.caCert != null) {
-              "connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
-              "connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
-            });
-          };
-        })
-        (optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
-          "socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
-          "subsystem=elytron" = mkOrder 900 {
-            "key-store=httpsKS" = mkOrder 900 {
-              path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
-              credential-reference.clear-text = "notsosecretpassword";
-              type = "JKS";
-            };
-            "key-manager=httpsKM" = mkOrder 901 {
-              key-store = "httpsKS";
-              credential-reference.clear-text = "notsosecretpassword";
-            };
-            "server-ssl-context=httpsSSC" = mkOrder 902 {
-              key-manager = "httpsKM";
-            };
-          };
-          "subsystem=undertow" = mkOrder 901 {
-            "server=default-server"."https-listener=https".ssl-context = "httpsSSC";
-          };
-        })
-        cfg.extraConfig
-      ];
-
-
-      /* Produces a JBoss CLI script that creates paths and sets
-         attributes matching those described by `attrs`. When the
-         script is run, the existing settings are effectively overlayed
-         by those from `attrs`. Existing attributes can be unset by
-         defining them `null`.
-
-         JBoss paths and attributes / maps are distinguished by their
-         name, where paths follow a `key=value` scheme.
-
-         Example:
-           mkJbossScript {
-             "subsystem=keycloak-server"."spi=hostname" = {
-               "provider=fixed" = null;
-               "provider=default" = {
-                 enabled = true;
-                 properties = {
-                   inherit frontendUrl;
-                   forceBackendUrlToFrontendUrl = false;
-                 };
-               };
-             };
-           }
-           => ''
-             if (outcome != success) of /:read-resource()
-                 /:add()
-             end-if
-             if (outcome != success) of /subsystem=keycloak-server:read-resource()
-                 /subsystem=keycloak-server:add()
-             end-if
-             if (outcome != success) of /subsystem=keycloak-server/spi=hostname:read-resource()
-                 /subsystem=keycloak-server/spi=hostname:add()
-             end-if
-             if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=default:read-resource()
-                 /subsystem=keycloak-server/spi=hostname/provider=default:add(enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" })
-             end-if
-             if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled")
-               /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true)
-             end-if
-             if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl")
-               /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false)
-             end-if
-             if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl")
-               /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth")
-             end-if
-             if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=fixed:read-resource()
-                 /subsystem=keycloak-server/spi=hostname/provider=fixed:remove()
-             end-if
-           ''
-      */
-      mkJbossScript = attrs:
-        let
-          /* From a JBoss path and an attrset, produces a JBoss CLI
-             snippet that writes the corresponding attributes starting
-             at `path`. Recurses down into subattrsets as necessary,
-             producing the variable name from its full path in the
-             attrset.
-
-             Example:
-               writeAttributes "/subsystem=keycloak-server/spi=hostname/provider=default" {
-                 enabled = true;
-                 properties = {
-                   forceBackendUrlToFrontendUrl = false;
-                   frontendUrl = "https://keycloak.example.com/auth";
-                 };
-               }
-               => ''
-                 if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled")
-                   /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true)
-                 end-if
-                 if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl")
-                   /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false)
-                 end-if
-                 if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl")
-                   /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth")
-                 end-if
-               ''
-          */
-          writeAttributes = path: set:
-            let
-              # JBoss expressions like `${var}` need to be prefixed
-              # with `expression` to evaluate.
-              prefixExpression = string:
-                let
-                  matchResult = match ''"\$\{.*}"'' string;
-                in
-                if matchResult != null then
-                  "expression " + string
-                else
-                  string;
-
-              writeAttribute = attribute: value:
-                let
-                  type = typeOf value;
-                in
-                if type == "set" then
-                  let
-                    names = attrNames value;
-                  in
-                  foldl' (text: name: text + (writeAttribute "${attribute}.${name}" value.${name})) "" names
-                else if value == null then ''
-                  if (outcome == success) of ${path}:read-attribute(name="${attribute}")
-                      ${path}:undefine-attribute(name="${attribute}")
-                  end-if
-                ''
-                else if elem type [ "string" "path" "bool" ] then
-                  let
-                    value' = if type == "bool" then boolToString value else ''"${value}"'';
-                  in
-                  ''
-                    if (result != ${prefixExpression value'}) of ${path}:read-attribute(name="${attribute}")
-                      ${path}:write-attribute(name=${attribute}, value=${value'})
-                    end-if
-                  ''
-                else throw "Unsupported type '${type}' for path '${path}'!";
-            in
-            concatStrings
-              (mapAttrsToList
-                (attribute: value: (writeAttribute attribute value))
-                set);
-
-
-          /* Produces an argument list for the JBoss `add()` function,
-             which adds a JBoss path and takes as its arguments the
-             required subpaths and attributes.
-
-             Example:
-               makeArgList {
-                 enabled = true;
-                 properties = {
-                   forceBackendUrlToFrontendUrl = false;
-                   frontendUrl = "https://keycloak.example.com/auth";
-                 };
-               }
-               => ''
-                 enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" }
-               ''
-          */
-          makeArgList = set:
-            let
-              makeArg = attribute: value:
-                let
-                  type = typeOf value;
-                in
-                if type == "set" then
-                  "${attribute} = { " + (makeArgList value) + " }"
-                else if elem type [ "string" "path" "bool" ] then
-                  "${attribute} = ${if type == "bool" then boolToString value else ''"${value}"''}"
-                else if value == null then
-                  ""
-                else
-                  throw "Unsupported type '${type}' for attribute '${attribute}'!";
-
-            in
-            concatStringsSep ", " (mapAttrsToList makeArg set);
-
-
-          /* Recurses into the `nodeValue` attrset. Only subattrsets that
-             are JBoss paths, i.e. follows the `key=value` format, are recursed
-             into - the rest are considered JBoss attributes / maps.
-          */
-          recurse = nodePath: nodeValue:
-            let
-              nodeContent =
-                if isAttrs nodeValue && nodeValue._type or "" == "order" then
-                  nodeValue.content
-                else
-                  nodeValue;
-              isPath = name:
-                let
-                  value = nodeContent.${name};
-                in
-                if (match ".*([=]).*" name) == [ "=" ] then
-                  if isAttrs value || value == null then
-                    true
-                  else
-                    throw "Parsing path '${concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!"
-                else
-                  false;
-              jbossPath = "/" + concatStringsSep "/" nodePath;
-              children = if !isAttrs nodeContent then { } else nodeContent;
-              subPaths = filter isPath (attrNames children);
-              getPriority = name:
-                let
-                  value = children.${name};
-                in
-                if value._type or "" == "order" then value.priority else 1000;
-              orderedSubPaths = sort (a: b: getPriority a < getPriority b) subPaths;
-              jbossAttrs = filterAttrs (name: _: !(isPath name)) children;
-              text =
-                if nodeContent != null then
-                  ''
-                    if (outcome != success) of ${jbossPath}:read-resource()
-                        ${jbossPath}:add(${makeArgList jbossAttrs})
-                    end-if
-                  '' + writeAttributes jbossPath jbossAttrs
-                else
-                  ''
-                    if (outcome == success) of ${jbossPath}:read-resource()
-                        ${jbossPath}:remove()
-                    end-if
-                  '';
-            in
-            text + concatMapStringsSep "\n" (name: recurse (nodePath ++ [ name ]) children.${name}) orderedSubPaths;
-        in
-        recurse [ ] attrs;
-
-      jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
-
-      keycloakConfig = pkgs.runCommand "keycloak-config"
-        {
-          nativeBuildInputs = [ cfg.package ];
-        }
-        ''
-          export JBOSS_BASE_DIR="$(pwd -P)";
-          export JBOSS_MODULEPATH="${cfg.package}/modules";
-          export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log";
-
-          cp -r ${cfg.package}/standalone/configuration .
-          chmod -R u+rwX ./configuration
-
-          mkdir -p {deployments,ssl}
-
-          standalone.sh&
-
-          attempt=1
-          max_attempts=30
-          while ! jboss-cli.sh --connect ':read-attribute(name=server-state)'; do
-              if [[ "$attempt" == "$max_attempts" ]]; then
-                  echo "ERROR: Could not connect to Keycloak after $attempt attempts! Failing.." >&2
-                  exit 1
-              fi
-              echo "Keycloak not fully started yet, retrying.. ($attempt/$max_attempts)"
-              sleep 1
-              (( attempt++ ))
-          done
-
-          jboss-cli.sh --connect --file=${jbossCliScript} --echo-command
+      keycloakConfig = lib.generators.toKeyValue {
+        mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
+          mkValueString = v: with builtins;
+            if isInt v then toString v
+            else if isString v then v
+            else if true == v then "true"
+            else if false == v then "false"
+            else if isSecret v then hashString "sha256" v._secret
+            else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+        };
+      };
 
-          cp configuration/standalone.xml $out
-        '';
+      isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+      filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings;
+      confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig);
+      keycloakBuild = cfg.package.override {
+        inherit confFile;
+        plugins = cfg.package.enabledPlugins ++ cfg.plugins;
+      };
     in
     mkIf cfg.enable
       {
@@ -681,7 +511,45 @@ in
           }
         ];
 
-        environment.systemPackages = [ cfg.package ];
+        environment.systemPackages = [ keycloakBuild ];
+
+        services.keycloak.settings =
+          let
+            postgresParams = concatStringsSep "&" (
+              optionals cfg.database.useSSL [
+                "ssl=true"
+              ] ++ optionals (cfg.database.caCert != null) [
+                "sslrootcert=${cfg.database.caCert}"
+                "sslmode=verify-ca"
+              ]
+            );
+            mariadbParams = concatStringsSep "&" ([
+              "characterEncoding=UTF-8"
+            ] ++ optionals cfg.database.useSSL [
+              "useSSL=true"
+              "requireSSL=true"
+              "verifyServerCertificate=true"
+            ] ++ optionals (cfg.database.caCert != null) [
+              "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}"
+              "trustCertificateKeyStorePassword=notsosecretpassword"
+            ]);
+            dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams;
+          in
+          mkMerge [
+            {
+              db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type;
+              db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
+              db-password._secret = cfg.database.passwordFile;
+              db-url-host = "${cfg.database.host}:${toString cfg.database.port}";
+              db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name;
+              db-url-properties = prefixUnlessEmpty "?" dbProps;
+              db-url = null;
+            }
+            (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
+              https-certificate-file = "/run/keycloak/ssl/ssl_cert";
+              https-certificate-key-file = "/run/keycloak/ssl/ssl_key";
+            })
+          ];
 
         systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
           after = [ "postgresql.service" ];
@@ -744,41 +612,37 @@ in
                 "mysql.service"
               ]
               else [ ];
+            secretPaths = catAttrs "_secret" (collect isSecret cfg.settings);
+            mkSecretReplacement = file: ''
+              replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf
+            '';
+            secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
           in
           {
             after = databaseServices;
             bindsTo = databaseServices;
             wantedBy = [ "multi-user.target" ];
             path = with pkgs; [
-              cfg.package
+              keycloakBuild
               openssl
               replace-secret
             ];
             environment = {
-              JBOSS_LOG_DIR = "/var/log/keycloak";
-              JBOSS_BASE_DIR = "/run/keycloak";
-              JBOSS_MODULEPATH = "${cfg.package}/modules";
+              KC_HOME_DIR = "/run/keycloak";
+              KC_CONF_DIR = "/run/keycloak/conf";
             };
             serviceConfig = {
-              LoadCredential = [
-                "db_password:${cfg.database.passwordFile}"
-              ] ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
-                "ssl_cert:${cfg.sslCertificate}"
-                "ssl_key:${cfg.sslCertificateKey}"
-              ];
+              LoadCredential =
+                map (p: "${baseNameOf p}:${p}") secretPaths
+                ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
+                  "ssl_cert:${cfg.sslCertificate}"
+                  "ssl_key:${cfg.sslCertificateKey}"
+                ];
               User = "keycloak";
               Group = "keycloak";
               DynamicUser = true;
-              RuntimeDirectory = map (p: "keycloak/" + p) [
-                "configuration"
-                "deployments"
-                "data"
-                "ssl"
-                "log"
-                "tmp"
-              ];
+              RuntimeDirectory = "keycloak";
               RuntimeDirectoryMode = 0700;
-              LogsDirectory = "keycloak";
               AmbientCapabilities = "CAP_NET_BIND_SERVICE";
             };
             script = ''
@@ -787,31 +651,30 @@ in
 
               umask u=rwx,g=,o=
 
-              install -m 0600 ${cfg.package}/standalone/configuration/*.properties /run/keycloak/configuration
-              install -T -m 0600 ${keycloakConfig} /run/keycloak/configuration/standalone.xml
+              ln -s ${themesBundle} /run/keycloak/themes
+              ln -s ${keycloakBuild}/providers /run/keycloak/
 
-              replace-secret '@db-password@' "$CREDENTIALS_DIRECTORY/db_password" /run/keycloak/configuration/standalone.xml
+              install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf
+
+              ${secretReplacements}
 
-              export JAVA_OPTS=-Djboss.server.config.user.dir=/run/keycloak/configuration
-              add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}'
             '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
-              pushd /run/keycloak/ssl/
-              cat "$CREDENTIALS_DIRECTORY/ssl_cert" <(echo) \
-                  "$CREDENTIALS_DIRECTORY/ssl_key" <(echo) \
-                  /etc/ssl/certs/ca-certificates.crt \
-                  > allcerts.pem
-              openssl pkcs12 -export -in "$CREDENTIALS_DIRECTORY/ssl_cert" -inkey "$CREDENTIALS_DIRECTORY/ssl_key" -chain \
-                             -name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \
-                             -CAfile allcerts.pem -passout pass:notsosecretpassword
-              popd
+              mkdir -p /run/keycloak/ssl
+              cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
             '' + ''
-              ${cfg.package}/bin/standalone.sh
+              export KEYCLOAK_ADMIN=admin
+              export KEYCLOAK_ADMIN_PASSWORD=${cfg.initialAdminPassword}
+              kc.sh start
             '';
           };
 
         services.postgresql.enable = mkDefault createLocalPostgreSQL;
         services.mysql.enable = mkDefault createLocalMySQL;
-        services.mysql.package = mkIf createLocalMySQL pkgs.mariadb;
+        services.mysql.package =
+          let
+            dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80;
+          in
+          mkIf createLocalMySQL (mkDefault dbPkg);
       };
 
   meta.doc = ./keycloak.xml;
diff --git a/nixos/modules/services/web-apps/keycloak.xml b/nixos/modules/services/web-apps/keycloak.xml
index cb706932f48..861756e33ac 100644
--- a/nixos/modules/services/web-apps/keycloak.xml
+++ b/nixos/modules/services/web-apps/keycloak.xml
@@ -27,10 +27,10 @@
 
      <para>
        Refer to the <link
-       xlink:href="https://www.keycloak.org/docs/latest/server_admin/index.html#admin-console">Admin
-       Console section of the Keycloak Server Administration Guide</link> for
-       information on how to administer your
-       <productname>Keycloak</productname> instance.
+       xlink:href="https://www.keycloak.org/docs/latest/server_admin/index.html">
+       Keycloak Server Administration Guide</link> for information on
+       how to administer your <productname>Keycloak</productname>
+       instance.
      </para>
    </section>
 
@@ -38,27 +38,28 @@
      <title>Database access</title>
      <para>
        <productname>Keycloak</productname> can be used with either
-       <productname>PostgreSQL</productname> or
+       <productname>PostgreSQL</productname>,
+       <productname>MariaDB</productname> or
        <productname>MySQL</productname>. Which one is used can be
        configured in <xref
        linkend="opt-services.keycloak.database.type" />. The selected
        database will automatically be enabled and a database and role
        created unless <xref
-       linkend="opt-services.keycloak.database.host" /> is changed from
-       its default of <literal>localhost</literal> or <xref
-       linkend="opt-services.keycloak.database.createLocally" /> is set
-       to <literal>false</literal>.
+       linkend="opt-services.keycloak.database.host" /> is changed
+       from its default of <literal>localhost</literal> or <xref
+       linkend="opt-services.keycloak.database.createLocally" /> is
+       set to <literal>false</literal>.
      </para>
 
      <para>
        External database access can also be configured by setting
        <xref linkend="opt-services.keycloak.database.host" />, <xref
+       linkend="opt-services.keycloak.database.name" />, <xref
        linkend="opt-services.keycloak.database.username" />, <xref
        linkend="opt-services.keycloak.database.useSSL" /> and <xref
        linkend="opt-services.keycloak.database.caCert" /> as
-       appropriate. Note that you need to manually create a database
-       called <literal>keycloak</literal> and allow the configured
-       database user full access to it.
+       appropriate. Note that you need to manually create the database
+       and allow the configured database user full access to it.
      </para>
 
      <para>
@@ -79,22 +80,27 @@
      </warning>
    </section>
 
-   <section xml:id="module-services-keycloak-frontendurl">
-     <title>Frontend URL</title>
+   <section xml:id="module-services-keycloak-hostname">
+     <title>Hostname</title>
      <para>
-       The frontend URL is used as base for all frontend requests and
-       must be configured through <xref linkend="opt-services.keycloak.frontendUrl" />.
-       It should normally include a trailing <literal>/auth</literal>
-       (the default web context). If you use a reverse proxy, you need
-       to set this option to <literal>""</literal>, so that frontend URL
-       is derived from HTTP headers. <literal>X-Forwarded-*</literal> headers
-       support also should be enabled, using <link
-       xlink:href="https://www.keycloak.org/docs/latest/server_installation/index.html#identifying-client-ip-addresses">
-       respective guidelines</link>.
+       The hostname is used to build the public URL used as base for
+       all frontend requests and must be configured through <xref
+       linkend="opt-services.keycloak.settings.hostname" />.
      </para>
 
+     <note>
+       <para>
+         If you're migrating an old Wildfly based Keycloak instance
+         and want to keep compatibility with your current clients,
+         you'll likely want to set <xref
+         linkend="opt-services.keycloak.settings.http-relative-path"
+         /> to <literal>/auth</literal>. See the option description
+         for more details.
+       </para>
+     </note>
+
      <para>
-       <xref linkend="opt-services.keycloak.forceBackendUrlToFrontendUrl" />
+       <xref linkend="opt-services.keycloak.settings.hostname-strict-backchannel" />
        determines whether Keycloak should force all requests to go
        through the frontend URL. By default,
        <productname>Keycloak</productname> allows backend requests to
@@ -104,10 +110,10 @@
      </para>
 
      <para>
-       See the <link
-       xlink:href="https://www.keycloak.org/docs/latest/server_installation/#_hostname">Hostname
-       section of the Keycloak Server Installation and Configuration
-       Guide</link> for more information.
+        For more information on hostname configuration, see the <link
+        xlink:href="https://www.keycloak.org/server/hostname">Hostname
+        section of the Keycloak Server Installation and Configuration
+        Guide</link>.
      </para>
    </section>
 
@@ -139,68 +145,40 @@
    <section xml:id="module-services-keycloak-themes">
      <title>Themes</title>
      <para>
-        You can package custom themes and make them visible to Keycloak via
-        <xref linkend="opt-services.keycloak.themes" />
-        option. See the <link xlink:href="https://www.keycloak.org/docs/latest/server_development/#_themes">
+        You can package custom themes and make them visible to
+        Keycloak through <xref linkend="opt-services.keycloak.themes"
+        />. See the <link
+        xlink:href="https://www.keycloak.org/docs/latest/server_development/#_themes">
         Themes section of the Keycloak Server Development Guide</link>
-        and respective NixOS option description for more information.
+        and the description of the aforementioned NixOS option for
+        more information.
      </para>
    </section>
 
-   <section xml:id="module-services-keycloak-extra-config">
-     <title>Additional configuration</title>
+   <section xml:id="module-services-keycloak-settings">
+     <title>Configuration file settings</title>
      <para>
-       Additional Keycloak configuration options, for which no
-       explicit <productname>NixOS</productname> options are provided,
-       can be set in <xref linkend="opt-services.keycloak.extraConfig" />.
+       Keycloak server configuration parameters can be set in <xref
+       linkend="opt-services.keycloak.settings" />. These correspond
+       directly to options in
+       <filename>conf/keycloak.conf</filename>. Some of the most
+       important parameters are documented as suboptions, the rest can
+       be found in the <link
+       xlink:href="https://www.keycloak.org/server/all-config">All
+       configuration section of the Keycloak Server Installation and
+       Configuration Guide</link>.
      </para>
 
      <para>
-       Options are expressed as a Nix attribute set which matches the
-       structure of the jboss-cli configuration. The configuration is
-       effectively overlayed on top of the default configuration
-       shipped with Keycloak. To remove existing nodes and undefine
-       attributes from the default configuration, set them to
-       <literal>null</literal>.
-     </para>
-     <para>
-       For example, the following script, which removes the hostname
-       provider <literal>default</literal>, adds the deprecated
-       hostname provider <literal>fixed</literal> and defines it the
-       default:
-
-<programlisting>
-/subsystem=keycloak-server/spi=hostname/provider=default:remove()
-/subsystem=keycloak-server/spi=hostname/provider=fixed:add(enabled = true, properties = { hostname = "keycloak.example.com" })
-/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="fixed")
-</programlisting>
-
-       would be expressed as
-
-<programlisting>
-services.keycloak.extraConfig = {
-  "subsystem=keycloak-server" = {
-    "spi=hostname" = {
-      "provider=default" = null;
-      "provider=fixed" = {
-        enabled = true;
-        properties.hostname = "keycloak.example.com";
-      };
-      default-provider = "fixed";
-    };
-  };
-};
-</programlisting>
-     </para>
-     <para>
-       You can discover available options by using the <link
-       xlink:href="http://docs.wildfly.org/21/Admin_Guide.html#Command_Line_Interface">jboss-cli.sh</link>
-       program and by referring to the <link
-       xlink:href="https://www.keycloak.org/docs/latest/server_installation/index.html">Keycloak
-       Server Installation and Configuration Guide</link>.
+       Options containing secret data should be set to an attribute
+       set containing the attribute <literal>_secret</literal> - a
+       string pointing to a file containing the value the option
+       should be set to. See the description of <xref
+       linkend="opt-services.keycloak.settings" /> for an example.
      </para>
    </section>
 
+
    <section xml:id="module-services-keycloak-example-config">
      <title>Example configuration</title>
      <para>
@@ -208,9 +186,11 @@ services.keycloak.extraConfig = {
 <programlisting>
 services.keycloak = {
   <link linkend="opt-services.keycloak.enable">enable</link> = true;
+  settings = {
+    <link linkend="opt-services.keycloak.settings.hostname">hostname</link> = "keycloak.example.com";
+    <link linkend="opt-services.keycloak.settings.hostname-strict-backchannel">hostname-strict-backchannel</link> = true;
+  };
   <link linkend="opt-services.keycloak.initialAdminPassword">initialAdminPassword</link> = "e6Wcm0RrtegMEHl";  # change on first login
-  <link linkend="opt-services.keycloak.frontendUrl">frontendUrl</link> = "https://keycloak.example.com/auth";
-  <link linkend="opt-services.keycloak.forceBackendUrlToFrontendUrl">forceBackendUrlToFrontendUrl</link> = true;
   <link linkend="opt-services.keycloak.sslCertificate">sslCertificate</link> = "/run/keys/ssl_cert";
   <link linkend="opt-services.keycloak.sslCertificateKey">sslCertificateKey</link> = "/run/keys/ssl_key";
   <link linkend="opt-services.keycloak.database.passwordFile">database.passwordFile</link> = "/run/keys/db_password";
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 8208c85bfd7..fbfcc33b2dc 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -9,6 +9,8 @@ let
     RAILS_ENV = "production";
     NODE_ENV = "production";
 
+    LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
+
     # mastodon-web concurrency.
     WEB_CONCURRENCY = toString cfg.webProcesses;
     MAX_THREADS = toString cfg.webThreads;
@@ -121,7 +123,7 @@ in {
 
           Make sure that websockets are forwarded properly. You might want to set up caching
           of some requests. Take a look at mastodon's provided nginx configuration at
-          <code>https://github.com/tootsuite/mastodon/blob/master/dist/nginx.conf</code>.
+          <code>https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf</code>.
         '';
         type = lib.types.bool;
         default = false;
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
new file mode 100644
index 00000000000..a7d8bede74b
--- /dev/null
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -0,0 +1,265 @@
+{ config, lib, pkgs, buildEnv, ... }:
+
+with lib;
+
+let
+  cfg = config.services.netbox;
+  staticDir = cfg.dataDir + "/static";
+  configFile = pkgs.writeTextFile {
+    name = "configuration.py";
+    text = ''
+      STATIC_ROOT = '${staticDir}'
+      ALLOWED_HOSTS = ['*']
+      DATABASE = {
+        'NAME': 'netbox',
+        'USER': 'netbox',
+        'HOST': '/run/postgresql',
+      }
+
+      # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
+      # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
+      # to use two separate database IDs.
+      REDIS = {
+          'tasks': {
+              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=0',
+              'SSL': False,
+          },
+          'caching': {
+              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=1',
+              'SSL': False,
+          }
+      }
+
+      with open("${cfg.secretKeyFile}", "r") as file:
+          SECRET_KEY = file.readline()
+
+      ${optionalString cfg.enableLdap "REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'"}
+
+      ${cfg.extraConfig}
+    '';
+  };
+  pkg = (pkgs.netbox.overrideAttrs (old: {
+    installPhase = old.installPhase + ''
+      ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
+    '' + optionalString cfg.enableLdap ''
+      ln -s ${ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
+    '';
+  })).override {
+    plugins = ps: ((cfg.plugins ps)
+      ++ optional cfg.enableLdap [ ps.django-auth-ldap ]);
+  };
+  netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" ''
+    #!${stdenv.shell}
+    export PYTHONPATH=${pkg.pythonPath}
+    sudo -u netbox ${pkg}/bin/netbox "$@"
+  '');
+
+in {
+  options.services.netbox = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable Netbox.
+
+        This module requires a reverse proxy that serves <literal>/static</literal> separately.
+        See this <link xlink:href="https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/">example</link> on how to configure this.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "[::1]";
+      description = ''
+        Address the server will listen on.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8001;
+      description = ''
+        Port the server will listen on.
+      '';
+    };
+
+    plugins = mkOption {
+      type = types.functionTo (types.listOf types.package);
+      default = _: [];
+      defaultText = literalExpression ''
+        python3Packages: with python3Packages; [];
+      '';
+      description = ''
+        List of plugin packages to install.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/netbox";
+      description = ''
+        Storage path of netbox.
+      '';
+    };
+
+    secretKeyFile = mkOption {
+      type = types.path;
+      description = ''
+        Path to a file containing the secret key.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Additional lines of configuration appended to the <literal>configuration.py</literal>.
+        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/configuration/optional-settings/">documentation</link> for more possible options.
+      '';
+    };
+
+    enableLdap = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable LDAP-Authentication for Netbox.
+
+        This requires a configuration file being pass through <literal>ldapConfigPath</literal>.
+      '';
+    };
+
+    ldapConfigPath = mkOption {
+      type = types.path;
+      default = "";
+      description = ''
+        Path to the Configuration-File for LDAP-Authentification, will be loaded as <literal>ldap_config.py</literal>.
+        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration">documentation</link> for possible options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.redis.servers.netbox.enable = true;
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "netbox" ];
+      ensureUsers = [
+        {
+          name = "netbox";
+          ensurePermissions = {
+            "DATABASE netbox" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+    };
+
+    environment.systemPackages = [ netboxManageScript ];
+
+    systemd.targets.netbox = {
+      description = "Target for all NetBox services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" "redis-netbox.service" ];
+    };
+
+    systemd.services = let
+      defaultServiceConfig = {
+        WorkingDirectory = "${cfg.dataDir}";
+        User = "netbox";
+        Group = "netbox";
+        StateDirectory = "netbox";
+        StateDirectoryMode = "0750";
+        Restart = "on-failure";
+      };
+    in {
+      netbox-migration = {
+        description = "NetBox migrations";
+        wantedBy = [ "netbox.target" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/netbox migrate
+          '';
+        };
+      };
+
+      netbox = {
+        description = "NetBox WSGI Service";
+        wantedBy = [ "netbox.target" ];
+        after = [ "netbox-migration.service" ];
+
+        preStart = ''
+          ${pkg}/bin/netbox trace_paths --no-input
+          ${pkg}/bin/netbox collectstatic --no-input
+          ${pkg}/bin/netbox remove_stale_contenttypes --no-input
+        '';
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkgs.python3Packages.gunicorn}/bin/gunicorn netbox.wsgi \
+              --bind ${cfg.listenAddress}:${toString cfg.port} \
+              --pythonpath ${pkg}/opt/netbox/netbox
+          '';
+        };
+      };
+
+      netbox-rq = {
+        description = "NetBox Request Queue Worker";
+        wantedBy = [ "netbox.target" ];
+        after = [ "netbox.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkg}/bin/netbox rqworker high default low
+          '';
+        };
+      };
+
+      netbox-housekeeping = {
+        description = "NetBox housekeeping job";
+        after = [ "netbox.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/netbox housekeeping
+          '';
+        };
+      };
+    };
+
+    systemd.timers.netbox-housekeeping = {
+      description = "Run NetBox housekeeping job";
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnCalendar = "daily";
+      };
+    };
+
+    users.users.netbox = {
+      home = "${cfg.dataDir}";
+      isSystemUser = true;
+      group = "netbox";
+    };
+    users.groups.netbox = {};
+    users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index b32220a5e57..a4b886821eb 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -251,6 +251,23 @@ in {
       '';
     };
 
+    database = {
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Create the database and database user locally. Only available for
+          mysql database.
+          Note that this option will use the latest version of MariaDB which
+          is not officially supported by Nextcloud. As for now a workaround
+          is used to also support MariaDB version >= 10.6.
+        '';
+      };
+
+    };
+
+
     config = {
       dbtype = mkOption {
         type = types.enum [ "sqlite" "pgsql" "mysql" ];
@@ -505,6 +522,29 @@ in {
         The nextcloud-occ program preconfigured to target this Nextcloud instance.
       '';
     };
+    globalProfiles = mkEnableOption "global profiles" // {
+      description = ''
+        Makes user-profiles globally available under <literal>nextcloud.tld/u/user.name</literal>.
+        Even though it's enabled by default in Nextcloud, it must be explicitly enabled
+        here because it has the side-effect that personal information is even accessible to
+        unauthenticated users by default.
+
+        By default, the following properties are set to <quote>Show to everyone</quote>
+        if this flag is enabled:
+        <itemizedlist>
+        <listitem><para>About</para></listitem>
+        <listitem><para>Full name</para></listitem>
+        <listitem><para>Headline</para></listitem>
+        <listitem><para>Organisation</para></listitem>
+        <listitem><para>Profile picture</para></listitem>
+        <listitem><para>Role</para></listitem>
+        <listitem><para>Twitter</para></listitem>
+        <listitem><para>Website</para></listitem>
+        </itemizedlist>
+
+        Only has an effect in Nextcloud 23 and later.
+      '';
+    };
 
     nginx.recommendedHttpHeaders = mkOption {
       type = types.bool;
@@ -583,6 +623,12 @@ in {
         else pkgs.php80;
     }
 
+    { assertions = [
+      { assertion = cfg.database.createLocally -> cfg.config.dbtype == "mysql";
+        message = ''services.nextcloud.config.dbtype must be set to mysql if services.nextcloud.database.createLocally is set to true.'';
+      }
+    ]; }
+
     { systemd.timers.nextcloud-cron = {
         wantedBy = [ "timers.target" ];
         timerConfig.OnBootSec = "5m";
@@ -627,6 +673,8 @@ in {
               if x == null then "false"
               else boolToString x;
 
+          nextcloudGreaterOrEqualThan = req: versionAtLeast cfg.package.version req;
+
           overrideConfig = pkgs.writeText "nextcloud-config.php" ''
             <?php
             ${optionalString requiresReadSecretFunction ''
@@ -666,6 +714,7 @@ in {
               'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
               'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
               ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
+              ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles}"}
               ${objectstoreConfig}
             ];
           '';
@@ -811,6 +860,32 @@ in {
 
       environment.systemPackages = [ occ ];
 
+      services.mysql = lib.mkIf cfg.database.createLocally {
+        enable = true;
+        package = lib.mkDefault pkgs.mariadb;
+        ensureDatabases = [ cfg.config.dbname ];
+        ensureUsers = [{
+          name = cfg.config.dbuser;
+          ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
+        }];
+        # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6,
+        # this is a workaround.
+        # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22
+        settings = {
+          mysqld = {
+            innodb_read_only_compressed = 0;
+          };
+        };
+        initialScript = pkgs.writeText "mysql-init" ''
+          CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
+          CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname};
+          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+            CREATE TEMPORARY TABLES ON ${cfg.config.dbname}.* TO '${cfg.config.dbuser}'@'localhost'
+            IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
+          FLUSH privileges;
+        '';
+      };
+
       services.nginx.enable = mkDefault true;
 
       services.nginx.virtualHosts.${cfg.hostName} = {
diff --git a/nixos/modules/services/web-apps/nifi.nix b/nixos/modules/services/web-apps/nifi.nix
new file mode 100644
index 00000000000..21a63127264
--- /dev/null
+++ b/nixos/modules/services/web-apps/nifi.nix
@@ -0,0 +1,318 @@
+{ lib, pkgs, config, options, ... }:
+
+let
+  cfg = config.services.nifi;
+  opt = options.services.nifi;
+
+  env = {
+    NIFI_OVERRIDE_NIFIENV = "true";
+    NIFI_HOME = "/var/lib/nifi";
+    NIFI_PID_DIR = "/run/nifi";
+    NIFI_LOG_DIR = "/var/log/nifi";
+  };
+
+  envFile = pkgs.writeText "nifi.env" (lib.concatMapStrings (s: s + "\n") (
+    (lib.concatLists (lib.mapAttrsToList (name: value:
+      if value != null then [
+        "${name}=\"${toString value}\""
+      ] else []
+    ) env))));
+
+  nifiEnv = pkgs.writeShellScriptBin "nifi-env" ''
+    set -a
+    source "${envFile}"
+    eval -- "\$@"
+  '';
+
+in {
+  options = {
+    services.nifi = {
+      enable = lib.mkEnableOption "Apache NiFi";
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.nifi;
+        defaultText = lib.literalExpression "pkgs.nifi";
+        description = "Apache NiFi package to use.";
+      };
+
+      user = lib.mkOption {
+        type = lib.types.str;
+        default = "nifi";
+        description = "User account where Apache NiFi runs.";
+      };
+
+      group = lib.mkOption {
+        type = lib.types.str;
+        default = "nifi";
+        description = "Group account where Apache NiFi runs.";
+      };
+
+      enableHTTPS = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = "Enable HTTPS protocol. Don`t use in production.";
+      };
+
+      listenHost = lib.mkOption {
+        type = lib.types.str;
+        default = if cfg.enableHTTPS then "0.0.0.0" else "127.0.0.1";
+        defaultText = lib.literalExpression ''
+          if config.${opt.enableHTTPS}
+          then "0.0.0.0"
+          else "127.0.0.1"
+        '';
+        description = "Bind to an ip for Apache NiFi web-ui.";
+      };
+
+      listenPort = lib.mkOption {
+        type = lib.types.int;
+        default = if cfg.enableHTTPS then 8443 else 8080;
+        defaultText = lib.literalExpression ''
+          if config.${opt.enableHTTPS}
+          then "8443"
+          else "8000"
+        '';
+        description = "Bind to a port for Apache NiFi web-ui.";
+      };
+
+      proxyHost = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = if cfg.enableHTTPS then "0.0.0.0" else null;
+        defaultText = lib.literalExpression ''
+          if config.${opt.enableHTTPS}
+          then "0.0.0.0"
+          else null
+        '';
+        description = "Allow requests from a specific host.";
+      };
+
+      proxyPort = lib.mkOption {
+        type = lib.types.nullOr lib.types.int;
+        default = if cfg.enableHTTPS then 8443 else null;
+        defaultText = lib.literalExpression ''
+          if config.${opt.enableHTTPS}
+          then "8443"
+          else null
+        '';
+        description = "Allow requests from a specific port.";
+      };
+
+      initUser = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = "Initial user account for Apache NiFi. Username must be at least 4 characters.";
+      };
+
+      initPasswordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/nifi/password-nifi";
+        description = "nitial password for Apache NiFi. Password must be at least 12 characters.";
+      };
+
+      initJavaHeapSize = lib.mkOption {
+        type = lib.types.nullOr lib.types.int;
+        default = null;
+        example = 1024;
+        description = "Set the initial heap size for the JVM in MB.";
+      };
+
+      maxJavaHeapSize = lib.mkOption {
+        type = lib.types.nullOr lib.types.int;
+        default = null;
+        example = 2048;
+        description = "Set the initial heap size for the JVM in MB.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.initUser!=null || cfg.initPasswordFile==null;
+          message = ''
+            <option>services.nifi.initUser</option> needs to be set if <option>services.nifi.initPasswordFile</option> enabled.
+          '';
+      }
+      { assertion = cfg.initUser==null || cfg.initPasswordFile!=null;
+          message = ''
+            <option>services.nifi.initPasswordFile</option> needs to be set if <option>services.nifi.initUser</option> enabled.
+          '';
+      }
+      { assertion = cfg.proxyHost==null || cfg.proxyPort!=null;
+          message = ''
+            <option>services.nifi.proxyPort</option> needs to be set if <option>services.nifi.proxyHost</option> value specified.
+          '';
+      }
+      { assertion = cfg.proxyHost!=null || cfg.proxyPort==null;
+          message = ''
+            <option>services.nifi.proxyHost</option> needs to be set if <option>services.nifi.proxyPort</option> value specified.
+          '';
+      }
+      { assertion = cfg.initJavaHeapSize==null || cfg.maxJavaHeapSize!=null;
+          message = ''
+            <option>services.nifi.maxJavaHeapSize</option> needs to be set if <option>services.nifi.initJavaHeapSize</option> value specified.
+          '';
+      }
+      { assertion = cfg.initJavaHeapSize!=null || cfg.maxJavaHeapSize==null;
+          message = ''
+            <option>services.nifi.initJavaHeapSize</option> needs to be set if <option>services.nifi.maxJavaHeapSize</option> value specified.
+          '';
+      }
+    ];
+
+    warnings = lib.optional (cfg.enableHTTPS==false) ''
+      Please do not disable HTTPS mode in production. In this mode, access to the nifi is opened without authentication.
+    '';
+
+    systemd.tmpfiles.rules = [
+      "d '/var/lib/nifi/conf' 0750 ${cfg.user} ${cfg.group}"
+      "L+ '/var/lib/nifi/lib' - - - - ${cfg.package}/lib"
+    ];
+
+
+    systemd.services.nifi = {
+      description = "Apache NiFi";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = env;
+      path = [ pkgs.gawk ];
+
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/nifi/nifi.pid";
+        ExecStartPre = pkgs.writeScript "nifi-pre-start.sh" ''
+          #!/bin/sh
+          umask 077
+          test -f '/var/lib/nifi/conf/authorizers.xml'                      || (cp '${cfg.package}/share/nifi/conf/authorizers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/authorizers.xml')
+          test -f '/var/lib/nifi/conf/bootstrap.conf'                       || (cp '${cfg.package}/share/nifi/conf/bootstrap.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap.conf')
+          test -f '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf'       || (cp '${cfg.package}/share/nifi/conf/bootstrap-hashicorp-vault.conf' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-hashicorp-vault.conf')
+          test -f '/var/lib/nifi/conf/bootstrap-notification-services.xml'  || (cp '${cfg.package}/share/nifi/conf/bootstrap-notification-services.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/bootstrap-notification-services.xml')
+          test -f '/var/lib/nifi/conf/logback.xml'                          || (cp '${cfg.package}/share/nifi/conf/logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/logback.xml')
+          test -f '/var/lib/nifi/conf/login-identity-providers.xml'         || (cp '${cfg.package}/share/nifi/conf/login-identity-providers.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/login-identity-providers.xml')
+          test -f '/var/lib/nifi/conf/nifi.properties'                      || (cp '${cfg.package}/share/nifi/conf/nifi.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/nifi.properties')
+          test -f '/var/lib/nifi/conf/stateless-logback.xml'                || (cp '${cfg.package}/share/nifi/conf/stateless-logback.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless-logback.xml')
+          test -f '/var/lib/nifi/conf/stateless.properties'                 || (cp '${cfg.package}/share/nifi/conf/stateless.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/stateless.properties')
+          test -f '/var/lib/nifi/conf/state-management.xml'                 || (cp '${cfg.package}/share/nifi/conf/state-management.xml' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/state-management.xml')
+          test -f '/var/lib/nifi/conf/zookeeper.properties'                 || (cp '${cfg.package}/share/nifi/conf/zookeeper.properties' '/var/lib/nifi/conf/' && chmod 0640 '/var/lib/nifi/conf/zookeeper.properties')
+          test -d '/var/lib/nifi/docs/html'                                 || (mkdir -p /var/lib/nifi/docs && cp -r '${cfg.package}/share/nifi/docs/html' '/var/lib/nifi/docs/html')
+          ${lib.optionalString ((cfg.initUser != null) && (cfg.initPasswordFile != null)) ''
+            awk -F'[<|>]' '/property name="Username"/ {if ($3!="") f=1} END{exit !f}' /var/lib/nifi/conf/login-identity-providers.xml || ${cfg.package}/bin/nifi.sh set-single-user-credentials ${cfg.initUser} $(cat ${cfg.initPasswordFile})
+          ''}
+          ${lib.optionalString (cfg.enableHTTPS == false) ''
+            sed -i /var/lib/nifi/conf/nifi.properties \
+              -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=false|g' \
+              -e 's|nifi.web.http.host=.*|nifi.web.http.host=${cfg.listenHost}|g' \
+              -e 's|nifi.web.http.port=.*|nifi.web.http.port=${(toString cfg.listenPort)}|g' \
+              -e 's|nifi.web.https.host=.*|nifi.web.https.host=|g' \
+              -e 's|nifi.web.https.port=.*|nifi.web.https.port=|g' \
+              -e 's|nifi.security.keystore=.*|nifi.security.keystore=|g' \
+              -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=|g' \
+              -e 's|nifi.security.truststore=.*|nifi.security.truststore=|g' \
+              -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=|g' \
+              -e '/nifi.security.keystorePasswd/s|^|#|' \
+              -e '/nifi.security.keyPasswd/s|^|#|' \
+              -e '/nifi.security.truststorePasswd/s|^|#|'
+          ''}
+          ${lib.optionalString (cfg.enableHTTPS == true) ''
+            sed -i /var/lib/nifi/conf/nifi.properties \
+              -e 's|nifi.remote.input.secure=.*|nifi.remote.input.secure=true|g' \
+              -e 's|nifi.web.http.host=.*|nifi.web.http.host=|g' \
+              -e 's|nifi.web.http.port=.*|nifi.web.http.port=|g' \
+              -e 's|nifi.web.https.host=.*|nifi.web.https.host=${cfg.listenHost}|g' \
+              -e 's|nifi.web.https.port=.*|nifi.web.https.port=${(toString cfg.listenPort)}|g' \
+              -e 's|nifi.security.keystore=.*|nifi.security.keystore=./conf/keystore.p12|g' \
+              -e 's|nifi.security.keystoreType=.*|nifi.security.keystoreType=PKCS12|g' \
+              -e 's|nifi.security.truststore=.*|nifi.security.truststore=./conf/truststore.p12|g' \
+              -e 's|nifi.security.truststoreType=.*|nifi.security.truststoreType=PKCS12|g' \
+              -e '/nifi.security.keystorePasswd/s|^#\+||' \
+              -e '/nifi.security.keyPasswd/s|^#\+||' \
+              -e '/nifi.security.truststorePasswd/s|^#\+||'
+          ''}
+          ${lib.optionalString ((cfg.enableHTTPS == true) && (cfg.proxyHost != null) && (cfg.proxyPort != null)) ''
+            sed -i /var/lib/nifi/conf/nifi.properties \
+              -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=${cfg.proxyHost}:${(toString cfg.proxyPort)}|g'
+          ''}
+          ${lib.optionalString ((cfg.enableHTTPS == false) || (cfg.proxyHost == null) && (cfg.proxyPort == null)) ''
+            sed -i /var/lib/nifi/conf/nifi.properties \
+              -e 's|nifi.web.proxy.host=.*|nifi.web.proxy.host=|g'
+          ''}
+          ${lib.optionalString ((cfg.initJavaHeapSize != null) && (cfg.maxJavaHeapSize != null))''
+            sed -i /var/lib/nifi/conf/bootstrap.conf \
+              -e 's|java.arg.2=.*|java.arg.2=-Xms${(toString cfg.initJavaHeapSize)}m|g' \
+              -e 's|java.arg.3=.*|java.arg.3=-Xmx${(toString cfg.maxJavaHeapSize)}m|g'
+          ''}
+          ${lib.optionalString ((cfg.initJavaHeapSize == null) && (cfg.maxJavaHeapSize == null))''
+            sed -i /var/lib/nifi/conf/bootstrap.conf \
+              -e 's|java.arg.2=.*|java.arg.2=-Xms512m|g' \
+              -e 's|java.arg.3=.*|java.arg.3=-Xmx512m|g'
+          ''}
+        '';
+        ExecStart = "${cfg.package}/bin/nifi.sh start";
+        ExecStop = "${cfg.package}/bin/nifi.sh stop";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Runtime directory and mode
+        RuntimeDirectory = "nifi";
+        RuntimeDirectoryMode = "0750";
+        # State directory and mode
+        StateDirectory = "nifi";
+        StateDirectoryMode = "0750";
+        # Logs directory and mode
+        LogsDirectory = "nifi";
+        LogsDirectoryMode = "0750";
+        # Proc filesystem
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        # Access write directories
+        ReadWritePaths = [ cfg.initPasswordFile ];
+        UMask = "0027";
+        # Capabilities
+        CapabilityBoundingSet = "";
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute  = false;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @resources @privileged @setuid" "@chown" ];
+      };
+    };
+
+    users.users = lib.mkMerge [
+      (lib.mkIf (cfg.user == "nifi") {
+        nifi = {
+          group = cfg.group;
+          isSystemUser = true;
+          home = cfg.package;
+        };
+      })
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package nifiEnv ])
+    ];
+
+    users.groups = lib.optionalAttrs (cfg.group == "nifi") {
+      nifi = { };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index d817ff6019a..3099705acbe 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -710,20 +710,15 @@ in
 
     services.logrotate = optionalAttrs (cfg.logFormat != "none") {
       enable = mkDefault true;
-      paths.httpd = {
-        path = "${cfg.logDir}/*.log";
-        user = cfg.user;
-        group = cfg.group;
+      settings.httpd = {
+        files = "${cfg.logDir}/*.log";
+        su = "${cfg.user} ${cfg.group}";
         frequency = "daily";
-        keep = 28;
-        extraConfig = ''
-          sharedscripts
-          compress
-          delaycompress
-          postrotate
-            systemctl reload httpd.service > /dev/null 2>/dev/null || true
-          endscript
-        '';
+        rotate = 28;
+        sharedscripts = true;
+        compress = true;
+        delaycompress = true;
+        postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
       };
     };
 
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index a4a5a435b2e..46f62a9119f 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -161,5 +161,5 @@ in with lib; {
     (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
   ];
 
-  meta.maintainers = with maintainers; [ chiiruno ];
+  meta.maintainers = with maintainers; [ Madouura ];
 }
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index e046c28dd6b..0c2333399e8 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -255,20 +255,22 @@ let
             else defaultListen;
 
         listenString = { addr, port, ssl, extraParameters ? [], ... }:
-          "listen ${addr}:${toString port} "
-          + optionalString ssl "ssl "
+          (if ssl && vhost.http3 then "
+          # UDP listener for **QUIC+HTTP/3
+          listen ${addr}:${toString port} http3 "
+          + optionalString vhost.default "default_server "
+          + optionalString vhost.reuseport "reuseport "
+          + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
+          + ";" else "")
+          + "
+
+            listen ${addr}:${toString port} "
           + optionalString (ssl && vhost.http2) "http2 "
+          + optionalString ssl "ssl "
           + optionalString vhost.default "default_server "
+          + optionalString vhost.reuseport "reuseport "
           + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
-          + ";"
-          + (if ssl && vhost.http3 then ''
-          # UDP listener for **QUIC+HTTP/3
-          listen ${addr}:${toString port} http3 reuseport;
-          # Advertise that HTTP/3 is available
-          add_header Alt-Svc 'h3=":443"';
-          # Sent when QUIC was used
-          add_header QUIC-Status $quic;
-          '' else "");
+          + ";";
 
         redirectListen = filter (x: !x.ssl) defaultListen;
 
@@ -321,6 +323,11 @@ let
             ssl_conf_command Options KTLS;
           ''}
 
+          ${optionalString (hasSSL && vhost.http3) ''
+            # Advertise that HTTP/3 is available
+            add_header Alt-Svc 'h3=":443"; ma=86400' always;
+          ''}
+
           ${mkBasicAuth vhostName vhost}
 
           ${mkLocations vhost.locations}
@@ -989,17 +996,14 @@ in
       nginx.gid = config.ids.gids.nginx;
     };
 
-    services.logrotate.paths.nginx = mapAttrs (_: mkDefault) {
-      path = "/var/log/nginx/*.log";
+    services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
+      files = "/var/log/nginx/*.log";
       frequency = "weekly";
-      keep = 26;
-      extraConfig = ''
-        compress
-        delaycompress
-        postrotate
-          [ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`
-        endscript
-      '';
+      su = "${cfg.user} ${cfg.group}";
+      rotate = 26;
+      compress = true;
+      delaycompress = true;
+      postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
     };
   };
 }
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index c4e8285dc48..2c77d6ee816 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -20,7 +20,7 @@ with lib;
     serverAliases = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = ["www.example.org" "example.org"];
+      example = [ "www.example.org" "example.org" ];
       description = ''
         Additional names of virtual hosts served by this virtual host configuration.
       '';
@@ -31,11 +31,11 @@ with lib;
         addr = mkOption { type = str;  description = "IP address.";  };
         port = mkOption { type = int;  description = "Port number."; default = 80; };
         ssl  = mkOption { type = bool; description = "Enable SSL.";  default = false; };
-        extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "reuseport" "deferred" ]; };
+        extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
       }; });
       default = [];
       example = [
-        { addr = "195.154.1.1"; port = 443; ssl = true;}
+        { addr = "195.154.1.1"; port = 443; ssl = true; }
         { addr = "192.154.1.1"; port = 80; }
       ];
       description = ''
@@ -207,6 +207,15 @@ with lib;
       '';
     };
 
+    reuseport = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Create an individual listening socket .
+        It is required to specify only once on one of the hosts.
+      '';
+    };
+
     root = mkOption {
       type = types.nullOr types.path;
       default = null;
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 3a78a526460..705dbec5e74 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -196,7 +196,7 @@ in
       programs.evince.enable = mkDefault true;
       programs.file-roller.enable = mkDefault true;
 
-      environment.systemPackages = (with pkgs // pkgs.gnome // pkgs.cinnamon; pkgs.gnome.removePackagesByName [
+      environment.systemPackages = with pkgs // pkgs.gnome // pkgs.cinnamon; utils.removePackagesByName [
         # cinnamon team apps
         bulky
         blueberry
@@ -212,7 +212,7 @@ in
         # external apps shipped with linux-mint
         hexchat
         gnome-calculator
-      ] config.environment.cinnamon.excludePackages);
+      ] config.environment.cinnamon.excludePackages;
     })
   ];
 }
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index d1513a596b9..654079023cb 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -92,6 +92,7 @@ in
 
     services.udisks2.enable = true;
     services.upower.enable = config.powerManagement.enable;
+    services.xserver.libinput.enable = mkDefault true;
 
     services.dbus.packages = [ e.efl ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index e2323785149..e7e626c66f0 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -22,6 +22,9 @@ let
     favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop' ]
   '';
 
+  nixos-background-ligtht = pkgs.nixos-artwork.wallpapers.simple-blue;
+  nixos-background-dark = pkgs.nixos-artwork.wallpapers.simple-dark-gray;
+
   nixos-gsettings-desktop-schemas = let
     defaultPackages = with pkgs; [ gsettings-desktop-schemas gnome.gnome-shell ];
   in
@@ -42,10 +45,11 @@ let
      chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
      cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
        [org.gnome.desktop.background]
-       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray.gnomeFilePath}'
+       picture-uri='file://${nixos-background-ligtht.gnomeFilePath}'
+       picture-uri-dark='file://${nixos-background-dark.gnomeFilePath}'
 
        [org.gnome.desktop.screensaver]
-       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath}'
+       picture-uri='file://${nixos-background-dark.gnomeFilePath}'
 
        ${cfg.favoriteAppsOverride}
 
@@ -55,6 +59,26 @@ let
      ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/
     '';
 
+  nixos-background-info = pkgs.writeTextFile rec {
+    name = "nixos-background-info";
+    text = ''
+      <?xml version="1.0"?>
+      <!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">
+      <wallpapers>
+        <wallpaper deleted="false">
+          <name>Blobs</name>
+          <filename>${nixos-background-ligtht.gnomeFilePath}</filename>
+          <filename-dark>${nixos-background-dark.gnomeFilePath}</filename-dark>
+          <options>zoom</options>
+          <shade_type>solid</shade_type>
+          <pcolor>#3a4ba0</pcolor>
+          <scolor>#2f302f</scolor>
+        </wallpaper>
+      </wallpapers>
+    '';
+    destination = "/share/gnome-background-properties/nixos.xml";
+  };
+
   flashbackEnabled = cfg.flashback.enableMetacity || length cfg.flashback.customSessions > 0;
   flashbackWms = optional cfg.flashback.enableMetacity {
     wmName = "metacity";
@@ -132,6 +156,10 @@ in
       [ "environment" "gnome3" "excludePackages" ]
       [ "environment" "gnome" "excludePackages" ]
     )
+    (mkRemovedOptionModule
+      [ "services" "gnome" "experimental-features" "realtime-scheduling" ]
+      "Set `security.rtkit.enable = true;` to make realtime scheduling possible. (Still needs to be enabled using GSettings.)"
+    )
   ];
 
   options = {
@@ -142,38 +170,6 @@ in
       core-utilities.enable = mkEnableOption "GNOME core utilities";
       core-developer-tools.enable = mkEnableOption "GNOME core developer tools";
       games.enable = mkEnableOption "GNOME games";
-
-      experimental-features = {
-        realtime-scheduling = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Makes mutter (which propagates to gnome-shell) request a low priority real-time
-            scheduling which is only available on the wayland session.
-            To enable this experimental feature it requires a restart of the compositor.
-            Note that enabling this option only enables the <emphasis>capability</emphasis>
-            for realtime-scheduling to be used. It doesn't automatically set the gsetting
-            so that mutter actually uses realtime-scheduling. This would require adding <literal>
-            rt-scheduler</literal> to <literal>/org/gnome/mutter/experimental-features</literal>
-            with dconf-editor. You cannot use extraGSettingsOverrides because that will only
-            change the default value of the setting.
-
-            Please be aware of these known issues with the feature in nixos:
-            <itemizedlist>
-             <listitem>
-              <para>
-               <link xlink:href="https://github.com/NixOS/nixpkgs/issues/90201">NixOS/nixpkgs#90201</link>
-              </para>
-             </listitem>
-             <listitem>
-              <para>
-               <link xlink:href="https://github.com/NixOS/nixpkgs/issues/86730">NixOS/nixpkgs#86730</link>
-              </para>
-            </listitem>
-            </itemizedlist>
-          '';
-        };
-      };
     };
 
     services.xserver.desktopManager.gnome = {
@@ -414,7 +410,6 @@ in
       services.gnome.rygel.enable = mkDefault true;
       services.gvfs.enable = true;
       services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
-      services.telepathy.enable = mkDefault true;
 
       systemd.packages = with pkgs.gnome; [
         gnome-session
@@ -459,6 +454,7 @@ in
       # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-shell.bst
       environment.systemPackages = with pkgs.gnome; [
         adwaita-icon-theme
+        nixos-background-info
         gnome-backgrounds
         gnome-bluetooth
         gnome-color-manager
@@ -467,8 +463,6 @@ in
         gnome-shell-extensions
         gnome-themes-extra
         pkgs.gnome-tour # GNOME Shell detects the .desktop file on first log-in.
-        pkgs.nixos-artwork.wallpapers.simple-dark-gray
-        pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom
         pkgs.gnome-user-docs
         pkgs.orca
         pkgs.glib # for gsettings
@@ -480,51 +474,28 @@ in
       ];
     })
 
-    # Enable soft realtime scheduling, only supported on wayland
-    (mkIf serviceCfg.experimental-features.realtime-scheduling {
-      security.wrappers.".gnome-shell-wrapped" = {
-        source = "${pkgs.gnome.gnome-shell}/bin/.gnome-shell-wrapped";
-        owner = "root";
-        group = "root";
-        capabilities = "cap_sys_nice=ep";
-      };
-
-      systemd.user.services.gnome-shell-wayland = let
-        gnomeShellRT = with pkgs.gnome; pkgs.runCommand "gnome-shell-rt" {} ''
-          mkdir -p $out/bin/
-          cp ${gnome-shell}/bin/gnome-shell $out/bin
-          sed -i "s@${gnome-shell}/bin/@${config.security.wrapperDir}/@" $out/bin/gnome-shell
-        '';
-      in {
-        # Note we need to clear ExecStart before overriding it
-        serviceConfig.ExecStart = ["" "${gnomeShellRT}/bin/gnome-shell"];
-        # Do not use the default environment, it provides a broken PATH
-        environment = mkForce {};
-      };
-    })
-
     # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-utilities.bst
     (mkIf serviceCfg.core-utilities.enable {
       environment.systemPackages =
         with pkgs.gnome;
-        removePackagesByName
+        utils.removePackagesByName
           ([
             baobab
             cheese
             eog
             epiphany
-            gedit
+            pkgs.gnome-text-editor
             gnome-calculator
             gnome-calendar
             gnome-characters
             gnome-clocks
+            pkgs.gnome-console
             gnome-contacts
             gnome-font-viewer
             gnome-logs
             gnome-maps
             gnome-music
             pkgs.gnome-photos
-            gnome-screenshot
             gnome-system-monitor
             gnome-weather
             nautilus
@@ -547,10 +518,13 @@ in
       programs.file-roller.enable = notExcluded pkgs.gnome.file-roller;
       programs.geary.enable = notExcluded pkgs.gnome.geary;
       programs.gnome-disks.enable = notExcluded pkgs.gnome.gnome-disk-utility;
-      programs.gnome-terminal.enable = notExcluded pkgs.gnome.gnome-terminal;
       programs.seahorse.enable = notExcluded pkgs.gnome.seahorse;
       services.gnome.sushi.enable = notExcluded pkgs.gnome.sushi;
 
+      # VTE shell integration for gnome-console
+      programs.bash.vteIntegration = mkDefault true;
+      programs.zsh.vteIntegration = mkDefault true;
+
       # Let nautilus find extensions
       # TODO: Create nautilus-with-extensions package
       environment.sessionVariables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
@@ -564,7 +538,7 @@ in
     })
 
     (mkIf serviceCfg.games.enable {
-      environment.systemPackages = (with pkgs.gnome; removePackagesByName [
+      environment.systemPackages = with pkgs.gnome; utils.removePackagesByName [
         aisleriot
         atomix
         five-or-more
@@ -585,12 +559,12 @@ in
         quadrapassel
         swell-foop
         tali
-      ] config.environment.gnome.excludePackages);
+      ] config.environment.gnome.excludePackages;
     })
 
     # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/-/blob/3.38.0/elements/core/meta-gnome-core-developer-tools.bst
     (mkIf serviceCfg.core-developer-tools.enable {
-      environment.systemPackages = (with pkgs.gnome; removePackagesByName [
+      environment.systemPackages = with pkgs.gnome; utils.removePackagesByName [
         dconf-editor
         devhelp
         pkgs.gnome-builder
@@ -599,7 +573,7 @@ in
         # in default configurations.
         # https://github.com/NixOS/nixpkgs/issues/60908
         /* gnome-boxes */
-      ] config.environment.gnome.excludePackages);
+      ] config.environment.gnome.excludePackages;
 
       services.sysprof.enable = notExcluded pkgs.sysprof;
     })
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 720985ba0d9..3c912d72611 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -51,7 +51,7 @@ in
     environment.systemPackages =
       pkgs.lxqt.preRequisitePackages ++
       pkgs.lxqt.corePackages ++
-      (pkgs.gnome.removePackagesByName
+      (utils.removePackagesByName
         pkgs.lxqt.optionalPackages
         config.environment.lxqt.excludePackages);
 
@@ -62,6 +62,9 @@ in
     services.gvfs.enable = true;
 
     services.upower.enable = config.powerManagement.enable;
+
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = [ pkgs.lxqt.xdg-desktop-portal-lxqt ];
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index a7fda4be979..9ab4c6e7e98 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -1,20 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
 let
 
-  addToXDGDirs = p: ''
-    if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
-      export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
-    fi
-
-    if [ -d "${p}/lib/girepository-1.0" ]; then
-      export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
-      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
-    fi
-  '';
-
   xcfg = config.services.xserver;
   cfg = xcfg.desktopManager.mate;
 
@@ -48,24 +37,8 @@ in
       pkgs.mate.mate-session-manager
     ];
 
-    services.xserver.displayManager.sessionCommands = ''
-      if test "$XDG_CURRENT_DESKTOP" = "MATE"; then
-          export XDG_MENU_PREFIX=mate-
-
-          # Let caja find extensions
-          export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0
-
-          # Let caja extensions find gsettings schemas
-          ${concatMapStrings (p: ''
-          if [ -d "${p}/lib/caja/extensions-2.0" ]; then
-              ${addToXDGDirs p}
-          fi
-          '') config.environment.systemPackages}
-
-          # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive)
-          ${addToXDGDirs pkgs.mate.mate-control-center}
-      fi
-    '';
+    # Let caja find extensions
+    environment.sessionVariables.CAJA_EXTENSION_DIRS = [ "${config.system.path}/lib/caja/extensions-2.0" ];
 
     # Let mate-panel find applets
     environment.sessionVariables."MATE_PANEL_APPLETS_DIR" = "${config.system.path}/share/mate-panel/applets";
@@ -74,7 +47,7 @@ in
     # Debugging
     environment.sessionVariables.MATE_SESSION_DEBUG = mkIf cfg.debug "1";
 
-    environment.systemPackages = pkgs.gnome.removePackagesByName
+    environment.systemPackages = utils.removePackagesByName
       (pkgs.mate.basePackages ++
       pkgs.mate.extraPackages ++
       [
@@ -83,7 +56,6 @@ in
         pkgs.gtk3.out
         pkgs.shared-mime-info
         pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
-        pkgs.mate.mate-settings-daemon
         pkgs.yelp # for 'Contents' in 'Help' menus
       ])
       config.environment.mate.excludePackages;
diff --git a/nixos/modules/services/x11/desktop-managers/none.nix b/nixos/modules/services/x11/desktop-managers/none.nix
index af7a376ae02..b5e498b67a0 100644
--- a/nixos/modules/services/x11/desktop-managers/none.nix
+++ b/nixos/modules/services/x11/desktop-managers/none.nix
@@ -1,7 +1,46 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  runXdgAutostart = config.services.xserver.desktopManager.runXdgAutostartIfNone;
+in
 {
-  services.xserver.desktopManager.session =
-    [ { name = "none";
-        start = "";
-      }
-    ];
+  options = {
+    services.xserver.desktopManager.runXdgAutostartIfNone = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run XDG autostart files for sessions without a desktop manager
+        (with only a window manager), these sessions usually don't handle XDG
+        autostart files by default.
+
+        Some services like <option>i18n.inputMethod</option> and
+        <option>service.earlyoom</option> use XDG autostart files to start.
+        If this option is not set to <literal>true</literal> and you are using
+        a window manager without a desktop manager, you need to manually start
+        them or running <package>dex</package> somewhere.
+      '';
+    };
+  };
+
+  config = mkMerge [
+    {
+      services.xserver.desktopManager.session = [
+        {
+          name = "none";
+          start = optionalString runXdgAutostart ''
+            /run/current-system/systemd/bin/systemctl --user start xdg-autostart-if-no-desktop-manager.target
+          '';
+        }
+      ];
+    }
+    (mkIf runXdgAutostart {
+      systemd.user.targets.xdg-autostart-if-no-desktop-manager = {
+        description = "Run XDG autostart files";
+        # From `plasma-workspace`, `share/systemd/user/plasma-workspace@.target`.
+        requires = [ "xdg-desktop-autostart.target" "graphical-session.target" ];
+        before = [ "xdg-desktop-autostart.target" "graphical-session.target" ];
+        bindsTo = [ "graphical-session.target" ];
+      };
+    })
+  ];
 }
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 8ff9b0b756d..004d14b634d 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, utils, pkgs, ... }:
 
 with lib;
 
@@ -214,15 +214,13 @@ in
         elementary-settings-daemon
         pantheon-agent-geoclue2
         pantheon-agent-polkit
-      ]) ++ (gnome.removePackagesByName [
+      ]) ++ (utils.removePackagesByName [
         gnome.gnome-font-viewer
         gnome.gnome-settings-daemon338
       ] config.environment.pantheon.excludePackages);
 
       programs.evince.enable = mkDefault true;
-      programs.evince.package = pkgs.pantheon.evince;
       programs.file-roller.enable = mkDefault true;
-      programs.file-roller.package = pkgs.pantheon.file-roller;
 
       # Settings from elementary-default-settings
       environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
@@ -274,7 +272,7 @@ in
     })
 
     (mkIf serviceCfg.apps.enable {
-      environment.systemPackages = with pkgs.pantheon; pkgs.gnome.removePackagesByName ([
+      environment.systemPackages = with pkgs.pantheon; utils.removePackagesByName ([
         elementary-calculator
         elementary-calendar
         elementary-camera
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml
index 202909d398f..6226f8f6a27 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml
@@ -3,7 +3,7 @@
          xml:id="chap-pantheon">
  <title>Pantheon Desktop</title>
  <para>
-  Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK 3 and Granite.
+  Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK and Granite.
  </para>
  <section xml:id="sec-pantheon-enable">
   <title>Enabling Pantheon</title>
@@ -89,9 +89,9 @@ switchboard-with-plugs.override {
      </para>
     </listitem>
    </varlistentry>
-   <varlistentry xml:id="sec-pantheon-faq-gnome3-and-pantheon">
+   <varlistentry xml:id="sec-pantheon-faq-gnome-and-pantheon">
     <term>
-     I cannot enable both GNOME 3 and Pantheon.
+     I cannot enable both GNOME and Pantheon.
     </term>
     <listitem>
      <para>
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index b7aa2eba81c..3ca044ad5bc 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -519,7 +519,7 @@ in
         with plasma5; with kdeApplications; with kdeFrameworks;
         [
           # Basic packages without which Plasma Mobile fails to work properly.
-          plasma-phone-components
+          plasma-mobile
           plasma-nano
           pkgs.maliit-framework
           pkgs.maliit-keyboard
@@ -573,7 +573,7 @@ in
         };
       };
 
-      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-phone-components ];
+      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-mobile ];
     })
   ];
 }
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 3cf92f98c56..88b21e59aaa 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -66,6 +66,12 @@ in
         default = true;
         description = "Enable the XFWM (default) window manager.";
       };
+
+      enableScreensaver = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Enable the XFCE screensaver.";
+      };
     };
   };
 
@@ -122,7 +128,7 @@ in
       ] ++ optionals (!cfg.noDesktop) [
         xfce4-panel
         xfdesktop
-      ];
+      ] ++ optional cfg.enableScreensaver xfce4-screensaver;
 
     environment.pathsToLink = [
       "/share/xfce4"
@@ -168,5 +174,6 @@ in
       xfce4-notifyd
     ];
 
+    security.pam.services.xfce4-screensaver.unixAuth = cfg.enableScreensaver;
   };
 }
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index b1dc6643be8..70ae6b8978d 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -141,7 +141,7 @@ in
           GDM_X_SERVER_EXTRA_ARGS = toString
             (filter (arg: arg != "-terminate") cfg.xserverArgs);
           # GDM is needed for gnome-login.session
-          XDG_DATA_DIRS = "${gdm}/share:${cfg.sessionData.desktops}/share";
+          XDG_DATA_DIRS = "${gdm}/share:${cfg.sessionData.desktops}/share:${pkgs.gnome.gnome-control-center}/share";
         } // optionalAttrs (xSessionWrapper != null) {
           # Make GDM use this wrapper before running the session, which runs the
           # configured setupCommands. This relies on a patched GDM which supports
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index 835b41d4ada..4d455fdf7b2 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -7,19 +7,26 @@ let
 in
 
 {
-  options = {
-    services.xserver.windowManager.qtile.enable = mkEnableOption "qtile";
+  options.services.xserver.windowManager.qtile = {
+    enable = mkEnableOption "qtile";
+
+    package = mkPackageOption pkgs "qtile" { };
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = [{
       name = "qtile";
       start = ''
-        ${pkgs.qtile}/bin/qtile start &
+        ${cfg.package}/bin/qtile start &
         waitPID=$!
       '';
     }];
 
-    environment.systemPackages = [ pkgs.qtile ];
+    environment.systemPackages = [
+      # pkgs.qtile is currently a buildenv of qtile and its dependencies.
+      # For userland commands, we want the underlying package so that
+      # packages such as python don't bleed into userland and overwrite intended behavior.
+      (cfg.package.unwrapped or cfg.package)
+    ];
   };
 }
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 0c50d82b23b..d488e9b55d4 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, utils, pkgs, ... }:
 
 with lib;
 
@@ -181,6 +181,13 @@ in
         '';
       };
 
+      excludePackages = mkOption {
+        default = [];
+        example = literalExpression "[ pkgs.xterm ]";
+        type = types.listOf types.package;
+        description = "Which X11 packages to exclude from the default environment";
+      };
+
       exportConfiguration = mkOption {
         type = types.bool;
         default = false;
@@ -655,7 +662,7 @@ in
           ${cfgPath}.source = xorg.xf86inputevdev.out + "/share" + cfgPath;
         });
 
-    environment.systemPackages =
+    environment.systemPackages = utils.removePackagesByName
       [ xorg.xorgserver.out
         xorg.xrandr
         xorg.xrdb
@@ -671,7 +678,7 @@ in
         pkgs.xdg-utils
         xorg.xf86inputevdev.out # get evdev.4 man page
         pkgs.nixos-icons # needed for gnome and pantheon about dialog, nixos-manual and maybe more
-      ]
+      ] config.services.xserver.excludePackages
       ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
 
     environment.pathsToLink = [ "/share/X11" ];
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 459d09faa53..3f0d976e70b 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -1,5 +1,17 @@
 #! @perl@/bin/perl
 
+# Issue #166838 uncovered a situation in which a configuration not suitable
+# for the target architecture caused a cryptic error message instead of
+# a clean failure. Due to this mismatch, the perl interpreter in the shebang
+# line wasn't able to be executed, causing this script to be misinterpreted
+# as a shell script.
+#
+# Let's detect this situation to give a more meaningful error
+# message. The following two lines are carefully written to be both valid Perl
+# and Bash.
+printf "Perl script erroneously interpreted as shell script,\ndoes target platform match nixpkgs.crossSystem platform?\n" && exit 1
+    if 0;
+
 use strict;
 use warnings;
 use Config::IniFiles;
@@ -8,18 +20,29 @@ use File::Basename;
 use File::Slurp qw(read_file write_file edit_file);
 use Net::DBus;
 use Sys::Syslog qw(:standard :macros);
-use Cwd 'abs_path';
+use Cwd qw(abs_path);
 
+## no critic(ControlStructures::ProhibitDeepNests)
+## no critic(ErrorHandling::RequireCarping)
 ## no critic(CodeLayout::ProhibitParensWithBuiltins)
+## no critic(Variables::ProhibitPunctuationVars, Variables::RequireLocalizedPunctuationVars)
+## no critic(InputOutput::RequireCheckedSyscalls, InputOutput::RequireBracedFileHandleWithPrint, InputOutput::RequireBriefOpen)
+## no critic(ValuesAndExpressions::ProhibitNoisyQuotes, ValuesAndExpressions::ProhibitMagicNumbers, ValuesAndExpressions::ProhibitEmptyQuotes, ValuesAndExpressions::ProhibitInterpolationOfLiterals)
+## no critic(RegularExpressions::ProhibitEscapedMetacharacters)
 
+# System closure path to switch to
 my $out = "@out@";
-
-my $curSystemd = abs_path("/run/current-system/sw/bin");
+# Path to the directory containing systemd tools of the old system
+my $cur_systemd = abs_path("/run/current-system/sw/bin");
+# Path to the systemd store path of the new system
+my $new_systemd = "@systemd@";
 
 # To be robust against interruption, record what units need to be started etc.
-my $startListFile = "/run/nixos/start-list";
-my $restartListFile = "/run/nixos/restart-list";
-my $reloadListFile = "/run/nixos/reload-list";
+# We read these files again every time this script starts to make sure we continue
+# where the old (interrupted) script left off.
+my $start_list_file = "/run/nixos/start-list";
+my $restart_list_file = "/run/nixos/restart-list";
+my $reload_list_file = "/run/nixos/reload-list";
 
 # Parse restart/reload requests by the activation script.
 # Activation scripts may write newline-separated units to the restart
@@ -31,21 +54,23 @@ my $reloadListFile = "/run/nixos/reload-list";
 # The reload file asks the script to reload a unit. This is the same as
 # specifying a reload trigger in the NixOS module and can be ignored if
 # the unit is restarted in this activation.
-my $restartByActivationFile = "/run/nixos/activation-restart-list";
-my $reloadByActivationFile = "/run/nixos/activation-reload-list";
-my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list";
-my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list";
-
-make_path("/run/nixos", { mode => oct(755) });
+my $restart_by_activation_file = "/run/nixos/activation-restart-list";
+my $reload_by_activation_file = "/run/nixos/activation-reload-list";
+my $dry_restart_by_activation_file = "/run/nixos/dry-activation-restart-list";
+my $dry_reload_by_activation_file = "/run/nixos/dry-activation-reload-list";
 
+# The action that is to be performed (like switch, boot, test, dry-activate)
+# Also exposed via environment variable from now on
 my $action = shift(@ARGV);
+$ENV{NIXOS_ACTION} = $action;
 
+# Expose the locale archive as an environment variable for systemctl and the activation script
 if ("@localeArchive@" ne "") {
     $ENV{LOCALE_ARCHIVE} = "@localeArchive@";
 }
 
 if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
-    print STDERR <<EOF;
+    print STDERR <<"EOF";
 Usage: $0 [switch|boot|test]
 
 switch:       make the configuration the boot default and activate now
@@ -56,37 +81,41 @@ EOF
     exit(1);
 }
 
-$ENV{NIXOS_ACTION} = $action;
-
 # This is a NixOS installation if it has /etc/NIXOS or a proper
 # /etc/os-release.
-die("This is not a NixOS installation!\n") unless
-    -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID="?nixos"?/s;
+if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // "") !~ /^ID="?nixos"?/msx) {
+    die("This is not a NixOS installation!\n");
+}
 
+make_path("/run/nixos", { mode => oct(755) });
 openlog("nixos", "", LOG_USER);
 
 # Install or update the bootloader.
 if ($action eq "switch" || $action eq "boot") {
-    chomp(my $installBootLoader = <<'EOFBOOTLOADER');
+    chomp(my $install_boot_loader = <<'EOFBOOTLOADER');
 @installBootLoader@
 EOFBOOTLOADER
-    system("$installBootLoader $out") == 0 or exit 1;
+    system("$install_boot_loader $out") == 0 or exit 1;
 }
 
 # Just in case the new configuration hangs the system, do a sync now.
-system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1";
+if (($ENV{"NIXOS_NO_SYNC"} // "") ne "1") {
+    system("@coreutils@/bin/sync", "-f", "/nix/store");
+}
 
-exit(0) if $action eq "boot";
+if ($action eq "boot") {
+    exit(0);
+}
 
 # Check if we can activate the new configuration.
-my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // "";
-my $newVersion = read_file("$out/init-interface-version");
+my $cur_init_interface_version = read_file("/run/current-system/init-interface-version", err_mode => "quiet") // "";
+my $new_init_interface_version = read_file("$out/init-interface-version");
 
-if ($newVersion ne $oldVersion) {
-    print STDERR <<EOF;
+if ($new_init_interface_version ne $cur_init_interface_version) {
+    print STDERR <<'EOF';
 Warning: the new NixOS configuration has an ‘init’ that is
 incompatible with the current configuration.  The new configuration
-won\'t take effect until you reboot the system.
+won't take effect until you reboot the system.
 EOF
     exit(100);
 }
@@ -95,41 +124,56 @@ EOF
 # virtual console 1 and we restart the "tty1" unit.
 $SIG{PIPE} = "IGNORE";
 
-sub getActiveUnits {
+# Asks the currently running systemd instance via dbus which units are active.
+# Returns a hash where the key is the name of each unit and the value a hash
+# of load, state, substate.
+sub get_active_units {
     my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
     my $units = $mgr->ListUnitsByPatterns([], []);
     my $res = {};
-    for my $item (@$units) {
+    for my $item (@{$units}) {
         my ($id, $description, $load_state, $active_state, $sub_state,
-            $following, $unit_path, $job_id, $job_type, $job_path) = @$item;
-        next unless $following eq '';
-        next if $job_id == 0 and $active_state eq 'inactive';
+            $following, $unit_path, $job_id, $job_type, $job_path) = @{$item};
+        if ($following ne "") {
+            next;
+        }
+        if ($job_id == 0 and $active_state eq "inactive") {
+            next;
+        }
         $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state };
     }
     return $res;
 }
 
-# Returns whether a systemd unit is active
+# Asks the currently running systemd instance whether a unit is currently active.
+# Takes the name of the unit as an argument and returns a bool whether the unit is active or not.
 sub unit_is_active {
     my ($unit_name) = @_;
 
-    my $mgr = Net::DBus->system->get_service('org.freedesktop.systemd1')->get_object('/org/freedesktop/systemd1');
+    my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
     my $units = $mgr->ListUnitsByNames([$unit_name]);
     if (scalar(@{$units}) == 0) {
         return 0;
     }
-    my $active_state = $units->[0]->[3]; ## no critic (ValuesAndExpressions::ProhibitMagicNumbers)
-    return $active_state eq 'active' || $active_state eq 'activating';
+    my $active_state = $units->[0]->[3];
+    return $active_state eq "active" || $active_state eq "activating";
 }
 
-sub parseFstab {
+# Parse a fstab file, given its path.
+# Returns a tuple of filesystems and swaps.
+#
+# Filesystems is a hash of mountpoint and { device, fsType, options }
+# Swaps is a hash of device and { options }
+sub parse_fstab {
     my ($filename) = @_;
     my ($fss, $swaps);
-    foreach my $line (read_file($filename, err_mode => 'quiet')) {
+    foreach my $line (read_file($filename, err_mode => "quiet")) {
         chomp($line);
-        $line =~ s/^\s*#.*//;
-        next if $line =~ /^\s*$/;
-        my @xs = split(/ /, $line);
+        $line =~ s/^\s*\#.*//msx;
+        if ($line =~ /^\s*$/msx) {
+            next;
+        }
+        my @xs = split(/\s+/msx, $line);
         if ($xs[2] eq "swap") {
             $swaps->{$xs[0]} = { options => $xs[3] // "" };
         } else {
@@ -148,35 +192,35 @@ sub parseFstab {
 #
 # Instead of returning the hash, this subroutine takes a hashref to return the data in. This
 # allows calling the subroutine multiple times with the same hash to parse override files.
-sub parseSystemdIni {
-    my ($unitContents, $path) = @_;
+sub parse_systemd_ini {
+    my ($unit_contents, $path) = @_;
     # Tie the ini file to a hash for easier access
-    tie(my %fileContents, 'Config::IniFiles', (-file => $path, -allowempty => 1, -allowcontinue => 1)); ## no critic(Miscellanea::ProhibitTies)
+    tie(my %file_contents, "Config::IniFiles", (-file => $path, -allowempty => 1, -allowcontinue => 1)); ## no critic(Miscellanea::ProhibitTies)
 
     # Copy over all sections
-    foreach my $sectionName (keys(%fileContents)) {
-        if ($sectionName eq "Install") {
+    foreach my $section_name (keys(%file_contents)) {
+        if ($section_name eq "Install") {
             # Skip the [Install] section because it has no relevant keys for us
             next;
         }
         # Copy over all keys
-        foreach my $iniKey (keys(%{$fileContents{$sectionName}})) {
+        foreach my $ini_key (keys(%{$file_contents{$section_name}})) {
             # Ensure the value is an array so it's easier to work with
-            my $iniValue = $fileContents{$sectionName}{$iniKey};
-            my @iniValues;
-            if (ref($iniValue) eq "ARRAY") {
-                @iniValues = @{$iniValue};
+            my $ini_value = $file_contents{$section_name}{$ini_key};
+            my @ini_values;
+            if (ref($ini_value) eq "ARRAY") {
+                @ini_values = @{$ini_value};
             } else {
-                @iniValues = $iniValue;
+                @ini_values = $ini_value;
             }
             # Go over all values
-            for my $iniValue (@iniValues) {
+            for my $ini_value (@ini_values) {
                 # If a value is empty, it's an override that tells us to clean the value
-                if ($iniValue eq "") {
-                    delete $unitContents->{$sectionName}->{$iniKey};
+                if ($ini_value eq "") {
+                    delete $unit_contents->{$section_name}->{$ini_key};
                     next;
                 }
-                push(@{$unitContents->{$sectionName}->{$iniKey}}, $iniValue);
+                push(@{$unit_contents->{$section_name}->{$ini_key}}, $ini_value);
             }
         }
     }
@@ -185,7 +229,7 @@ sub parseSystemdIni {
 
 # This subroutine takes the path to a systemd configuration file (like a unit configuration),
 # parses it, and returns a hash that contains the contents. The contents of this hash are
-# explained in the `parseSystemdIni` subroutine. Neither the sections nor the keys inside
+# explained in the `parse_systemd_ini` subroutine. Neither the sections nor the keys inside
 # the sections are consistently sorted.
 #
 # If a directory with the same basename ending in .d exists next to the unit file, it will be
@@ -199,36 +243,44 @@ sub parse_unit {
     # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\"
     $unit_path =~ s/\\/\\\\/gmsx;
     foreach (glob("${unit_path}{,.d/*.conf}")) {
-        parseSystemdIni(\%unit_data, "$_")
+        parse_systemd_ini(\%unit_data, "$_")
     }
     return %unit_data;
 }
 
 # Checks whether a specified boolean in a systemd unit is true
 # or false, with a default that is applied when the value is not set.
-sub parseSystemdBool {
-    my ($unitConfig, $sectionName, $boolName, $default) = @_;
+sub parse_systemd_bool {
+    my ($unit_config, $section_name, $bool_name, $default) = @_;
 
-    my @values = @{$unitConfig->{$sectionName}{$boolName} // []};
+    my @values = @{$unit_config->{$section_name}{$bool_name} // []};
     # Return default if value is not set
-    if (scalar(@values) lt 1 || not defined($values[-1])) {
+    if ((scalar(@values) < 1) || (not defined($values[-1]))) {
         return $default;
     }
     # If value is defined multiple times, use the last definition
-    my $last = $values[-1];
+    my $last_value = $values[-1];
     # These are valid values as of systemd.syntax(7)
-    return $last eq "1" || $last eq "yes" || $last eq "true" || $last eq "on";
+    return $last_value eq "1" || $last_value eq "yes" || $last_value eq "true" || $last_value eq "on";
 }
 
-sub recordUnit {
+# Writes a unit name into a given file to be more resilient against
+# crashes of the script. Does nothing when the action is dry-activate.
+sub record_unit {
     my ($fn, $unit) = @_;
-    write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate";
+    if ($action ne "dry-activate") {
+        write_file($fn, { append => 1 }, "$unit\n");
+    }
+    return;
 }
 
-# The opposite of recordUnit, removes a unit name from a file
+# The opposite of record_unit, removes a unit name from a file
 sub unrecord_unit {
     my ($fn, $unit) = @_;
-    edit_file(sub { s/^$unit\n//msx }, $fn) if $action ne "dry-activate";
+    if ($action ne "dry-activate") {
+        edit_file(sub { s/^$unit\n//msx }, $fn);
+    }
+    return;
 }
 
 # Compare the contents of two unit files and return whether the unit
@@ -240,8 +292,8 @@ sub unrecord_unit {
 # - 0 if the units are equal
 # - 1 if the units are different and a restart action is required
 # - 2 if the units are different and a reload action is required
-sub compare_units {
-    my ($old_unit, $new_unit) = @_;
+sub compare_units { ## no critic(Subroutines::ProhibitExcessComplexity)
+    my ($cur_unit, $new_unit) = @_;
     my $ret = 0;
     # Keys to ignore in the [Unit] section
     my %unit_section_ignores = map { $_ => 1 } qw(
@@ -262,13 +314,13 @@ sub compare_units {
     # Comparison hash for the sections
     my %section_cmp = map { $_ => 1 } keys(%{$new_unit});
     # Iterate over the sections
-    foreach my $section_name (keys(%{$old_unit})) {
+    foreach my $section_name (keys(%{$cur_unit})) {
         # Missing section in the new unit?
         if (not exists($section_cmp{$section_name})) {
             # If the [Unit] section was removed, make sure that only keys
             # were in it that are ignored
-            if ($section_name eq 'Unit') {
-                foreach my $ini_key (keys(%{$old_unit->{'Unit'}})) {
+            if ($section_name eq "Unit") {
+                foreach my $ini_key (keys(%{$cur_unit->{"Unit"}})) {
                     if (not defined($unit_section_ignores{$ini_key})) {
                         return 1;
                     }
@@ -277,7 +329,7 @@ sub compare_units {
             } else {
                 return 1;
             }
-            if ($section_name eq 'Unit' and %{$old_unit->{'Unit'}} == 1 and defined(%{$old_unit->{'Unit'}}{'X-Reload-Triggers'})) {
+            if ($section_name eq "Unit" and %{$cur_unit->{"Unit"}} == 1 and defined(%{$cur_unit->{"Unit"}}{"X-Reload-Triggers"})) {
                 # If a new [Unit] section was removed that only contained X-Reload-Triggers,
                 # do nothing.
                 next;
@@ -289,23 +341,23 @@ sub compare_units {
         # Comparison hash for the section contents
         my %ini_cmp = map { $_ => 1 } keys(%{$new_unit->{$section_name}});
         # Iterate over the keys of the section
-        foreach my $ini_key (keys(%{$old_unit->{$section_name}})) {
+        foreach my $ini_key (keys(%{$cur_unit->{$section_name}})) {
             delete $ini_cmp{$ini_key};
-            my @old_value = @{$old_unit->{$section_name}{$ini_key}};
+            my @cur_value = @{$cur_unit->{$section_name}{$ini_key}};
             # If the key is missing in the new unit, they are different...
             if (not $new_unit->{$section_name}{$ini_key}) {
                 # ... unless the key that is now missing is one of the ignored keys
-                if ($section_name eq 'Unit' and defined($unit_section_ignores{$ini_key})) {
+                if ($section_name eq "Unit" and defined($unit_section_ignores{$ini_key})) {
                     next;
                 }
                 return 1;
             }
             my @new_value = @{$new_unit->{$section_name}{$ini_key}};
             # If the contents are different, the units are different
-            if (not $comp_array->(\@old_value, \@new_value)) {
+            if (not $comp_array->(\@cur_value, \@new_value)) {
                 # Check if only the reload triggers changed or one of the ignored keys
-                if ($section_name eq 'Unit') {
-                    if ($ini_key eq 'X-Reload-Triggers') {
+                if ($section_name eq "Unit") {
+                    if ($ini_key eq "X-Reload-Triggers") {
                         $ret = 2;
                         next;
                     } elsif (defined($unit_section_ignores{$ini_key})) {
@@ -315,11 +367,11 @@ sub compare_units {
                 return 1;
             }
         }
-        # A key was introduced that was missing in the old unit
+        # A key was introduced that was missing in the previous unit
         if (%ini_cmp) {
-            if ($section_name eq 'Unit') {
+            if ($section_name eq "Unit") {
                 foreach my $ini_key (keys(%ini_cmp)) {
-                    if ($ini_key eq 'X-Reload-Triggers') {
+                    if ($ini_key eq "X-Reload-Triggers") {
                         $ret = 2;
                     } elsif (defined($unit_section_ignores{$ini_key})) {
                         next;
@@ -332,13 +384,13 @@ sub compare_units {
             }
         };
     }
-    # A section was introduced that was missing in the old unit
+    # A section was introduced that was missing in the previous unit
     if (%section_cmp) {
-        if (%section_cmp == 1 and defined($section_cmp{'Unit'})) {
-            foreach my $ini_key (keys(%{$new_unit->{'Unit'}})) {
+        if (%section_cmp == 1 and defined($section_cmp{"Unit"})) {
+            foreach my $ini_key (keys(%{$new_unit->{"Unit"}})) {
                 if (not defined($unit_section_ignores{$ini_key})) {
                     return 1;
-                } elsif ($ini_key eq 'X-Reload-Triggers') {
+                } elsif ($ini_key eq "X-Reload-Triggers") {
                     $ret = 2;
                 }
             }
@@ -350,76 +402,78 @@ sub compare_units {
     return $ret;
 }
 
-sub handleModifiedUnit {
-    my ($unit, $baseName, $newUnitFile, $newUnitInfo, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_;
+# Called when a unit exists in both the old systemd and the new system and the units
+# differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped.
+sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity)
+    my ($unit, $base_name, $new_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_;
 
-    if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/ || $unit =~ /\.slice$/) {
+    if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) {
         # Do nothing.  These cannot be restarted directly.
 
         # Slices and Paths don't have to be restarted since
         # properties (resource limits and inotify watches)
         # seem to get applied on daemon-reload.
-    } elsif ($unit =~ /\.mount$/) {
+    } elsif ($unit =~ /\.mount$/msx) {
         # Reload the changed mount unit to force a remount.
         # FIXME: only reload when Options= changed, restart otherwise
-        $unitsToReload->{$unit} = 1;
-        recordUnit($reloadListFile, $unit);
-    } elsif ($unit =~ /\.socket$/) {
+        $units_to_reload->{$unit} = 1;
+        record_unit($reload_list_file, $unit);
+    } elsif ($unit =~ /\.socket$/msx) {
         # FIXME: do something?
         # Attempt to fix this: https://github.com/NixOS/nixpkgs/pull/141192
         # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
         # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
     } else {
-        my %unitInfo = $newUnitInfo ? %{$newUnitInfo} : parse_unit($newUnitFile);
-        if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0) and not $unitsToRestart->{$unit} and not $unitsToStop->{$unit}) {
-            $unitsToReload->{$unit} = 1;
-            recordUnit($reloadListFile, $unit);
+        my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file);
+        if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) {
+            $units_to_reload->{$unit} = 1;
+            record_unit($reload_list_file, $unit);
         }
-        elsif (!parseSystemdBool(\%unitInfo, "Service", "X-RestartIfChanged", 1) || parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStop", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) {
-            $unitsToSkip->{$unit} = 1;
+        elsif (!parse_systemd_bool(\%new_unit_info, "Service", "X-RestartIfChanged", 1) || parse_systemd_bool(\%new_unit_info, "Unit", "RefuseManualStop", 0) || parse_systemd_bool(\%new_unit_info, "Unit", "X-OnlyManualStart", 0)) {
+            $units_to_skip->{$unit} = 1;
         } else {
             # It doesn't make sense to stop and start non-services because
             # they can't have ExecStop=
-            if (!parseSystemdBool(\%unitInfo, "Service", "X-StopIfChanged", 1) || $unit !~ /\.service$/) {
+            if (!parse_systemd_bool(\%new_unit_info, "Service", "X-StopIfChanged", 1) || $unit !~ /\.service$/msx) {
                 # This unit should be restarted instead of
                 # stopped and started.
-                $unitsToRestart->{$unit} = 1;
-                recordUnit($restartListFile, $unit);
+                $units_to_restart->{$unit} = 1;
+                record_unit($restart_list_file, $unit);
                 # Remove from units to reload so we don't restart and reload
-                if ($unitsToReload->{$unit}) {
-                    delete $unitsToReload->{$unit};
-                    unrecord_unit($reloadListFile, $unit);
+                if ($units_to_reload->{$unit}) {
+                    delete $units_to_reload->{$unit};
+                    unrecord_unit($reload_list_file, $unit);
                 }
             } else {
                 # If this unit is socket-activated, then stop the
                 # socket unit(s) as well, and restart the
                 # socket(s) instead of the service.
                 my $socket_activated = 0;
-                if ($unit =~ /\.service$/) {
-                    my @sockets = split(/ /, join(" ", @{$unitInfo{Service}{Sockets} // []}));
+                if ($unit =~ /\.service$/msx) {
+                    my @sockets = split(/\s+/msx, join(" ", @{$new_unit_info{Service}{Sockets} // []}));
                     if (scalar(@sockets) == 0) {
-                        @sockets = ("$baseName.socket");
+                        @sockets = ("$base_name.socket");
                     }
                     foreach my $socket (@sockets) {
-                        if (defined($activePrev->{$socket})) {
+                        if (defined($active_cur->{$socket})) {
                             # We can now be sure this is a socket-activate unit
 
-                            $unitsToStop->{$socket} = 1;
+                            $units_to_stop->{$socket} = 1;
                             # Only restart sockets that actually
                             # exist in new configuration:
                             if (-e "$out/etc/systemd/system/$socket") {
-                                $unitsToStart->{$socket} = 1;
-                                if ($unitsToStart eq $unitsToRestart) {
-                                    recordUnit($restartListFile, $socket);
+                                $units_to_start->{$socket} = 1;
+                                if ($units_to_start eq $units_to_restart) {
+                                    record_unit($restart_list_file, $socket);
                                 } else {
-                                    recordUnit($startListFile, $socket);
+                                    record_unit($start_list_file, $socket);
                                 }
                                 $socket_activated = 1;
                             }
                             # Remove from units to reload so we don't restart and reload
-                            if ($unitsToReload->{$unit}) {
-                                delete $unitsToReload->{$unit};
-                                unrecord_unit($reloadListFile, $unit);
+                            if ($units_to_reload->{$unit}) {
+                                delete $units_to_reload->{$unit};
+                                unrecord_unit($reload_list_file, $unit);
                             }
                         }
                     }
@@ -430,64 +484,67 @@ sub handleModifiedUnit {
                 # We write this to a file to ensure that the
                 # service gets restarted if we're interrupted.
                 if (!$socket_activated) {
-                    $unitsToStart->{$unit} = 1;
-                    if ($unitsToStart eq $unitsToRestart) {
-                        recordUnit($restartListFile, $unit);
+                    $units_to_start->{$unit} = 1;
+                    if ($units_to_start eq $units_to_restart) {
+                        record_unit($restart_list_file, $unit);
                     } else {
-                        recordUnit($startListFile, $unit);
+                        record_unit($start_list_file, $unit);
                     }
                 }
 
-                $unitsToStop->{$unit} = 1;
+                $units_to_stop->{$unit} = 1;
                 # Remove from units to reload so we don't restart and reload
-                if ($unitsToReload->{$unit}) {
-                    delete $unitsToReload->{$unit};
-                    unrecord_unit($reloadListFile, $unit);
+                if ($units_to_reload->{$unit}) {
+                    delete $units_to_reload->{$unit};
+                    unrecord_unit($reload_list_file, $unit);
                 }
             }
         }
     }
+    return;
 }
 
 # Figure out what units need to be stopped, started, restarted or reloaded.
-my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload);
+my (%units_to_stop, %units_to_skip, %units_to_start, %units_to_restart, %units_to_reload);
 
-my %unitsToFilter; # units not shown
+my %units_to_filter; # units not shown
 
-$unitsToStart{$_} = 1 foreach
-    split('\n', read_file($startListFile, err_mode => 'quiet') // "");
+%units_to_start = map { $_ => 1 }
+    split(/\n/msx, read_file($start_list_file, err_mode => "quiet") // "");
 
-$unitsToRestart{$_} = 1 foreach
-    split('\n', read_file($restartListFile, err_mode => 'quiet') // "");
+%units_to_restart = map { $_ => 1 }
+    split(/\n/msx, read_file($restart_list_file, err_mode => "quiet") // "");
 
-$unitsToReload{$_} = 1 foreach
-    split('\n', read_file($reloadListFile, err_mode => 'quiet') // "");
+%units_to_reload = map { $_ => 1 }
+    split(/\n/msx, read_file($reload_list_file, err_mode => "quiet") // "");
 
-my $activePrev = getActiveUnits();
-while (my ($unit, $state) = each(%{$activePrev})) {
-    my $baseUnit = $unit;
+my $active_cur = get_active_units();
+while (my ($unit, $state) = each(%{$active_cur})) {
+    my $base_unit = $unit;
 
-    my $prevUnitFile = "/etc/systemd/system/$baseUnit";
-    my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    my $cur_unit_file = "/etc/systemd/system/$base_unit";
+    my $new_unit_file = "$out/etc/systemd/system/$base_unit";
 
     # Detect template instances.
-    if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
-      $baseUnit = "$1\@.$2";
-      $prevUnitFile = "/etc/systemd/system/$baseUnit";
-      $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
+      $base_unit = "$1\@.$2";
+      $cur_unit_file = "/etc/systemd/system/$base_unit";
+      $new_unit_file = "$out/etc/systemd/system/$base_unit";
     }
 
-    my $baseName = $baseUnit;
-    $baseName =~ s/\.[a-z]*$//;
+    my $base_name = $base_unit;
+    $base_name =~ s/\.[[:lower:]]*$//msx;
 
-    if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
-        if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
-            my %unitInfo = parse_unit($prevUnitFile);
-            $unitsToStop{$unit} = 1 if parseSystemdBool(\%unitInfo, "Unit", "X-StopOnRemoval", 1);
+    if (-e $cur_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) {
+        if (! -e $new_unit_file || abs_path($new_unit_file) eq "/dev/null") {
+            my %cur_unit_info = parse_unit($cur_unit_file);
+            if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) {
+                $units_to_stop{$unit} = 1;
+            }
         }
 
-        elsif ($unit =~ /\.target$/) {
-            my %unitInfo = parse_unit($newUnitFile);
+        elsif ($unit =~ /\.target$/msx) {
+            my %new_unit_info = parse_unit($new_unit_file);
 
             # Cause all active target units to be restarted below.
             # This should start most changed units we stop here as
@@ -496,11 +553,11 @@ while (my ($unit, $state) = each(%{$activePrev})) {
             # active after the system has resumed, which probably
             # should not be the case.  Just ignore it.
             if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") {
-                unless (parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStart", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) {
-                    $unitsToStart{$unit} = 1;
-                    recordUnit($startListFile, $unit);
+                if (!(parse_systemd_bool(\%new_unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%new_unit_info, "Unit", "X-OnlyManualStart", 0))) {
+                    $units_to_start{$unit} = 1;
+                    record_unit($start_list_file, $unit);
                     # Don't spam the user with target units that always get started.
-                    $unitsToFilter{$unit} = 1;
+                    $units_to_filter{$unit} = 1;
                 }
             }
 
@@ -515,33 +572,35 @@ while (my ($unit, $state) = each(%{$activePrev})) {
             # Stopping a target generally has no effect on other units
             # (unless there is a PartOf dependency), so this is just a
             # bookkeeping thing to get systemd to do the right thing.
-            if (parseSystemdBool(\%unitInfo, "Unit", "X-StopOnReconfiguration", 0)) {
-                $unitsToStop{$unit} = 1;
+            if (parse_systemd_bool(\%new_unit_info, "Unit", "X-StopOnReconfiguration", 0)) {
+                $units_to_stop{$unit} = 1;
             }
         }
 
         else {
-            my %old_unit_info = parse_unit($prevUnitFile);
-            my %new_unit_info = parse_unit($newUnitFile);
-            my $diff = compare_units(\%old_unit_info, \%new_unit_info);
+            my %cur_unit_info = parse_unit($cur_unit_file);
+            my %new_unit_info = parse_unit($new_unit_file);
+            my $diff = compare_units(\%cur_unit_info, \%new_unit_info);
             if ($diff == 1) {
-                handleModifiedUnit($unit, $baseName, $newUnitFile, \%new_unit_info, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
-            } elsif ($diff == 2 and not $unitsToRestart{$unit}) {
-                $unitsToReload{$unit} = 1;
-                recordUnit($reloadListFile, $unit);
+                handle_modified_unit($unit, $base_name, $new_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip);
+            } elsif ($diff == 2 and not $units_to_restart{$unit}) {
+                $units_to_reload{$unit} = 1;
+                record_unit($reload_list_file, $unit);
             }
         }
     }
 }
 
-sub pathToUnitName {
+# Converts a path to the name of a systemd mount unit that would be responsible
+# for mounting this path.
+sub path_to_unit_name {
     my ($path) = @_;
     # Use current version of systemctl binary before daemon is reexeced.
-    open(my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path)
+    open(my $cmd, "-|", "$cur_systemd/systemd-escape", "--suffix=mount", "-p", $path)
         or die "Unable to escape $path!\n";
-    my $escaped = join("", <$cmd>);
+    my $escaped = do { local $/ = undef; <$cmd> };
     chomp($escaped);
-    close($cmd) or die('Unable to close systemd-escape pipe');
+    close($cmd) or die("Unable to close systemd-escape pipe");
     return $escaped;
 }
 
@@ -550,31 +609,31 @@ sub pathToUnitName {
 # automatically by starting local-fs.target.  FIXME: might be nicer if
 # we generated units for all mounts; then we could unify this with the
 # unit checking code above.
-my ($prevFss, $prevSwaps) = parseFstab("/etc/fstab");
-my ($newFss, $newSwaps) = parseFstab("$out/etc/fstab");
-foreach my $mountPoint (keys(%$prevFss)) {
-    my $prev = $prevFss->{$mountPoint};
-    my $new = $newFss->{$mountPoint};
-    my $unit = pathToUnitName($mountPoint);
+my ($cur_fss, $cur_swaps) = parse_fstab("/etc/fstab");
+my ($new_fss, $new_swaps) = parse_fstab("$out/etc/fstab");
+foreach my $mount_point (keys(%{$cur_fss})) {
+    my $cur = $cur_fss->{$mount_point};
+    my $new = $new_fss->{$mount_point};
+    my $unit = path_to_unit_name($mount_point);
     if (!defined($new)) {
         # Filesystem entry disappeared, so unmount it.
-        $unitsToStop{$unit} = 1;
-    } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) {
+        $units_to_stop{$unit} = 1;
+    } elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) {
         # Filesystem type or device changed, so unmount and mount it.
-        $unitsToStop{$unit} = 1;
-        $unitsToStart{$unit} = 1;
-        recordUnit($startListFile, $unit);
-    } elsif ($prev->{options} ne $new->{options}) {
+        $units_to_stop{$unit} = 1;
+        $units_to_start{$unit} = 1;
+        record_unit($start_list_file, $unit);
+    } elsif ($cur->{options} ne $new->{options}) {
         # Mount options changes, so remount it.
-        $unitsToReload{$unit} = 1;
-        recordUnit($reloadListFile, $unit);
+        $units_to_reload{$unit} = 1;
+        record_unit($reload_list_file, $unit);
     }
 }
 
 # Also handles swap devices.
-foreach my $device (keys(%$prevSwaps)) {
-    my $prev = $prevSwaps->{$device};
-    my $new = $newSwaps->{$device};
+foreach my $device (keys(%{$cur_swaps})) {
+    my $cur = $cur_swaps->{$device};
+    my $new = $new_swaps->{$device};
     if (!defined($new)) {
         # Swap entry disappeared, so turn it off.  Can't use
         # "systemctl stop" here because systemd has lots of alias
@@ -592,97 +651,109 @@ foreach my $device (keys(%$prevSwaps)) {
 
 
 # Should we have systemd re-exec itself?
-my $prevSystemd = abs_path("/proc/1/exe") // "/unknown";
-my $prevSystemdSystemConfig = abs_path("/etc/systemd/system.conf") // "/unknown";
-my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die;
-my $newSystemdSystemConfig = abs_path("$out/etc/systemd/system.conf") // "/unknown";
+my $cur_pid1_path = abs_path("/proc/1/exe") // "/unknown";
+my $cur_systemd_system_config = abs_path("/etc/systemd/system.conf") // "/unknown";
+my $new_pid1_path = abs_path("$new_systemd/lib/systemd/systemd") or die;
+my $new_systemd_system_config = abs_path("$out/etc/systemd/system.conf") // "/unknown";
 
-my $restartSystemd = $prevSystemd ne $newSystemd;
-if ($prevSystemdSystemConfig ne $newSystemdSystemConfig) {
-    $restartSystemd = 1;
+my $restart_systemd = $cur_pid1_path ne $new_pid1_path;
+if ($cur_systemd_system_config ne $new_systemd_system_config) {
+    $restart_systemd = 1;
 }
 
-
-sub filterUnits {
+# Takes an array of unit names and returns an array with the same elements,
+# except all units that are also in the global variable `unitsToFilter`.
+sub filter_units {
     my ($units) = @_;
     my @res;
     foreach my $unit (sort(keys(%{$units}))) {
-        push(@res, $unit) if !defined($unitsToFilter{$unit});
+        if (!defined($units_to_filter{$unit})) {
+            push(@res, $unit);
+        }
     }
     return @res;
 }
 
-my @unitsToStopFiltered = filterUnits(\%unitsToStop);
+my @units_to_stop_filtered = filter_units(\%units_to_stop);
 
 
 # Show dry-run actions.
 if ($action eq "dry-activate") {
-    print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n"
-        if scalar(@unitsToStopFiltered) > 0;
-    print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys(%unitsToSkip))), "\n"
-        if scalar(keys(%unitsToSkip)) > 0;
+    if (scalar(@units_to_stop_filtered) > 0) {
+        print STDERR "would stop the following units: ", join(", ", @units_to_stop_filtered), "\n";
+    }
+    if (scalar(keys(%units_to_skip)) > 0) {
+        print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys(%units_to_skip))), "\n";
+    }
 
     print STDERR "would activate the configuration...\n";
     system("$out/dry-activate", "$out");
 
     # Handle the activation script requesting the restart or reload of a unit.
-    foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) {
+    foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) {
         my $unit = $_;
-        my $baseUnit = $unit;
-        my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+        my $base_unit = $unit;
+        my $new_unit_file = "$out/etc/systemd/system/$base_unit";
 
         # Detect template instances.
-        if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
-          $baseUnit = "$1\@.$2";
-          $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+        if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
+          $base_unit = "$1\@.$2";
+          $new_unit_file = "$out/etc/systemd/system/$base_unit";
         }
 
-        my $baseName = $baseUnit;
-        $baseName =~ s/\.[a-z]*$//;
+        my $base_name = $base_unit;
+        $base_name =~ s/\.[[:lower:]]*$//msx;
 
         # Start units if they were not active previously
-        if (not defined($activePrev->{$unit})) {
-            $unitsToStart{$unit} = 1;
+        if (not defined($active_cur->{$unit})) {
+            $units_to_start{$unit} = 1;
             next;
         }
 
-        handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
+        handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
     }
-    unlink($dryRestartByActivationFile);
+    unlink($dry_restart_by_activation_file);
 
-    foreach (split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // "")) {
+    foreach (split(/\n/msx, read_file($dry_reload_by_activation_file, err_mode => "quiet") // "")) {
         my $unit = $_;
 
-        if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) {
-            $unitsToReload{$unit} = 1;
-            recordUnit($reloadListFile, $unit);
+        if (defined($active_cur->{$unit}) and not $units_to_restart{$unit} and not $units_to_stop{$unit}) {
+            $units_to_reload{$unit} = 1;
+            record_unit($reload_list_file, $unit);
         }
     }
-    unlink($dryReloadByActivationFile);
+    unlink($dry_reload_by_activation_file);
 
-    print STDERR "would restart systemd\n" if $restartSystemd;
-    print STDERR "would reload the following units: ", join(", ", sort(keys(%unitsToReload))), "\n"
-        if scalar(keys(%unitsToReload)) > 0;
-    print STDERR "would restart the following units: ", join(", ", sort(keys(%unitsToRestart))), "\n"
-        if scalar(keys(%unitsToRestart)) > 0;
-    my @unitsToStartFiltered = filterUnits(\%unitsToStart);
-    print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n"
-        if scalar(@unitsToStartFiltered);
+    if ($restart_systemd) {
+        print STDERR "would restart systemd\n";
+    }
+    if (scalar(keys(%units_to_reload)) > 0) {
+        print STDERR "would reload the following units: ", join(", ", sort(keys(%units_to_reload))), "\n";
+    }
+    if (scalar(keys(%units_to_restart)) > 0) {
+        print STDERR "would restart the following units: ", join(", ", sort(keys(%units_to_restart))), "\n";
+    }
+    my @units_to_start_filtered = filter_units(\%units_to_start);
+    if (scalar(@units_to_start_filtered)) {
+        print STDERR "would start the following units: ", join(", ", @units_to_start_filtered), "\n";
+    }
     exit 0;
 }
 
 
 syslog(LOG_NOTICE, "switching to system configuration $out");
 
-if (scalar(keys(%unitsToStop)) > 0) {
-    print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
-        if scalar(@unitsToStopFiltered);
+if (scalar(keys(%units_to_stop)) > 0) {
+    if (scalar(@units_to_stop_filtered)) {
+        print STDERR "stopping the following units: ", join(", ", @units_to_stop_filtered), "\n";
+    }
     # Use current version of systemctl binary before daemon is reexeced.
-    system("$curSystemd/systemctl", "stop", "--", sort(keys(%unitsToStop)));
+    system("$cur_systemd/systemctl", "stop", "--", sort(keys(%units_to_stop)));
 }
 
-print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys(%unitsToSkip))), "\n"
-    if scalar(keys(%unitsToSkip)) > 0;
+if (scalar(keys(%units_to_skip)) > 0) {
+    print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys(%units_to_skip))), "\n";
+}
 
 # Activate the new configuration (i.e., update /etc, make accounts,
 # and so on).
@@ -691,108 +762,110 @@ print STDERR "activating the configuration...\n";
 system("$out/activate", "$out") == 0 or $res = 2;
 
 # Handle the activation script requesting the restart or reload of a unit.
-foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) {
+foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) {
     my $unit = $_;
-    my $baseUnit = $unit;
-    my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    my $base_unit = $unit;
+    my $new_unit_file = "$out/etc/systemd/system/$base_unit";
 
     # Detect template instances.
-    if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
-      $baseUnit = "$1\@.$2";
-      $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) {
+      $base_unit = "$1\@.$2";
+      $new_unit_file = "$out/etc/systemd/system/$base_unit";
     }
 
-    my $baseName = $baseUnit;
-    $baseName =~ s/\.[a-z]*$//;
+    my $base_name = $base_unit;
+    $base_name =~ s/\.[[:lower:]]*$//msx;
 
     # Start units if they were not active previously
-    if (not defined($activePrev->{$unit})) {
-        $unitsToStart{$unit} = 1;
-        recordUnit($startListFile, $unit);
+    if (not defined($active_cur->{$unit})) {
+        $units_to_start{$unit} = 1;
+        record_unit($start_list_file, $unit);
         next;
     }
 
-    handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
+    handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip);
 }
 # We can remove the file now because it has been propagated to the other restart/reload files
-unlink($restartByActivationFile);
+unlink($restart_by_activation_file);
 
-foreach (split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // "")) {
+foreach (split(/\n/msx, read_file($reload_by_activation_file, err_mode => "quiet") // "")) {
     my $unit = $_;
 
-    if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) {
-        $unitsToReload{$unit} = 1;
-        recordUnit($reloadListFile, $unit);
+    if (defined($active_cur->{$unit}) and not $units_to_restart{$unit} and not $units_to_stop{$unit}) {
+        $units_to_reload{$unit} = 1;
+        record_unit($reload_list_file, $unit);
     }
 }
 # We can remove the file now because it has been propagated to the other reload file
-unlink($reloadByActivationFile);
+unlink($reload_by_activation_file);
 
 # Restart systemd if necessary. Note that this is done using the
 # current version of systemd, just in case the new one has trouble
 # communicating with the running pid 1.
-if ($restartSystemd) {
+if ($restart_systemd) {
     print STDERR "restarting systemd...\n";
-    system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2;
+    system("$cur_systemd/systemctl", "daemon-reexec") == 0 or $res = 2;
 }
 
 # Forget about previously failed services.
-system("@systemd@/bin/systemctl", "reset-failed");
+system("$new_systemd/bin/systemctl", "reset-failed");
 
 # Make systemd reload its units.
-system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
+system("$new_systemd/bin/systemctl", "daemon-reload") == 0 or $res = 3;
 
 # Reload user units
-open(my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend');
-while (my $f = <$listActiveUsers>) {
-    next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/;
+open(my $list_active_users, "-|", "$new_systemd/bin/loginctl", "list-users", "--no-legend") || die("Unable to call loginctl");
+while (my $f = <$list_active_users>) {
+    if ($f !~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/msx) {
+        next;
+    }
     my ($uid, $name) = ($+{uid}, $+{user});
     print STDERR "reloading user units for $name...\n";
 
     system("@su@", "-s", "@shell@", "-l", $name, "-c",
            "export XDG_RUNTIME_DIR=/run/user/$uid; " .
-           "$curSystemd/systemctl --user daemon-reexec; " .
-           "@systemd@/bin/systemctl --user start nixos-activation.service");
+           "$cur_systemd/systemctl --user daemon-reexec; " .
+           "$new_systemd/bin/systemctl --user start nixos-activation.service");
 }
 
-close($listActiveUsers);
+close($list_active_users) || die("Unable to close the file handle to loginctl");
 
 # Set the new tmpfiles
 print STDERR "setting up tmpfiles\n";
-system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
+system("$new_systemd/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
 
 # Before reloading we need to ensure that the units are still active. They may have been
 # deactivated because one of their requirements got stopped. If they are inactive
 # but should have been reloaded, the user probably expects them to be started.
-if (scalar(keys(%unitsToReload)) > 0) {
-    for my $unit (keys(%unitsToReload)) {
+if (scalar(keys(%units_to_reload)) > 0) {
+    for my $unit (keys(%units_to_reload)) {
         if (!unit_is_active($unit)) {
             # Figure out if we need to start the unit
             my %unit_info = parse_unit("$out/etc/systemd/system/$unit");
-            if (!(parseSystemdBool(\%unit_info, 'Unit', 'RefuseManualStart', 0) || parseSystemdBool(\%unit_info, 'Unit', 'X-OnlyManualStart', 0))) {
-                $unitsToStart{$unit} = 1;
-                recordUnit($startListFile, $unit);
+            if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) {
+                $units_to_start{$unit} = 1;
+                record_unit($start_list_file, $unit);
             }
             # Don't reload the unit, reloading would fail
-            delete %unitsToReload{$unit};
-            unrecord_unit($reloadListFile, $unit);
+            delete %units_to_reload{$unit};
+            unrecord_unit($reload_list_file, $unit);
         }
     }
 }
 # Reload units that need it. This includes remounting changed mount
 # units.
-if (scalar(keys(%unitsToReload)) > 0) {
-    print STDERR "reloading the following units: ", join(", ", sort(keys(%unitsToReload))), "\n";
-    system("@systemd@/bin/systemctl", "reload", "--", sort(keys(%unitsToReload))) == 0 or $res = 4;
-    unlink($reloadListFile);
+if (scalar(keys(%units_to_reload)) > 0) {
+    print STDERR "reloading the following units: ", join(", ", sort(keys(%units_to_reload))), "\n";
+    system("$new_systemd/bin/systemctl", "reload", "--", sort(keys(%units_to_reload))) == 0 or $res = 4;
+    unlink($reload_list_file);
 }
 
 # Restart changed services (those that have to be restarted rather
 # than stopped and started).
-if (scalar(keys(%unitsToRestart)) > 0) {
-    print STDERR "restarting the following units: ", join(", ", sort(keys(%unitsToRestart))), "\n";
-    system("@systemd@/bin/systemctl", "restart", "--", sort(keys(%unitsToRestart))) == 0 or $res = 4;
-    unlink($restartListFile);
+if (scalar(keys(%units_to_restart)) > 0) {
+    print STDERR "restarting the following units: ", join(", ", sort(keys(%units_to_restart))), "\n";
+    system("$new_systemd/bin/systemctl", "restart", "--", sort(keys(%units_to_restart))) == 0 or $res = 4;
+    unlink($restart_list_file);
 }
 
 # Start all active targets, as well as changed units we stopped above.
@@ -801,17 +874,18 @@ if (scalar(keys(%unitsToRestart)) > 0) {
 # that are symlinks to other units.  We shouldn't start both at the
 # same time because we'll get a "Failed to add path to set" error from
 # systemd.
-my @unitsToStartFiltered = filterUnits(\%unitsToStart);
-print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n"
-    if scalar(@unitsToStartFiltered);
-system("@systemd@/bin/systemctl", "start", "--", sort(keys(%unitsToStart))) == 0 or $res = 4;
-unlink($startListFile);
+my @units_to_start_filtered = filter_units(\%units_to_start);
+if (scalar(@units_to_start_filtered)) {
+    print STDERR "starting the following units: ", join(", ", @units_to_start_filtered), "\n"
+}
+system("$new_systemd/bin/systemctl", "start", "--", sort(keys(%units_to_start))) == 0 or $res = 4;
+unlink($start_list_file);
 
 
 # Print failed and new units.
 my (@failed, @new);
-my $activeNew = getActiveUnits();
-while (my ($unit, $state) = each(%{$activeNew})) {
+my $active_new = get_active_units();
+while (my ($unit, $state) = each(%{$active_new})) {
     if ($state->{state} eq "failed") {
         push(@failed, $unit);
         next;
@@ -819,7 +893,9 @@ while (my ($unit, $state) = each(%{$activeNew})) {
 
     if ($state->{substate} eq "auto-restart") {
         # A unit in auto-restart substate is a failure *if* it previously failed to start
-        my $main_status = `@systemd@/bin/systemctl show --value --property=ExecMainStatus '$unit'`;
+        open(my $main_status_fd, "-|", "$new_systemd/bin/systemctl", "show", "--value", "--property=ExecMainStatus", $unit) || die("Unable to call 'systemctl show'");
+        my $main_status = do { local $/ = undef; <$main_status_fd> };
+        close($main_status_fd) || die("Unable to close 'systemctl show' fd");
         chomp($main_status);
 
         if ($main_status ne "0") {
@@ -831,7 +907,7 @@ while (my ($unit, $state) = each(%{$activeNew})) {
     # Ignore scopes since they are not managed by this script but rather
     # created and managed by third-party services via the systemd dbus API.
     # This only lists units that are not failed (including ones that are in auto-restart but have not failed previously)
-    if ($state->{state} ne "failed" && !defined($activePrev->{$unit}) && $unit !~ /\.scope$/msx) {
+    if ($state->{state} ne "failed" && !defined($active_cur->{$unit}) && $unit !~ /\.scope$/msx) {
         push(@new, $unit);
     }
 }
@@ -843,7 +919,7 @@ if (scalar(@new) > 0) {
 if (scalar(@failed) > 0) {
     my @failed_sorted = sort(@failed);
     print STDERR "warning: the following units failed: ", join(", ", @failed_sorted), "\n\n";
-    system("@systemd@/bin/systemctl status --no-pager --full '" . join("' '", @failed_sorted) . "' >&2");
+    system("$new_systemd/bin/systemctl status --no-pager --full '" . join("' '", @failed_sorted) . "' >&2");
     $res = 4;
 }
 
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index b8aeee8c11b..84f560691fc 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -55,11 +55,18 @@ let
       substituteInPlace $out/dry-activate --subst-var out
       chmod u+x $out/activate $out/dry-activate
       unset activationScript dryActivationScript
-      ${pkgs.stdenv.shellDryRun} $out/activate
-      ${pkgs.stdenv.shellDryRun} $out/dry-activate
 
-      cp ${config.system.build.bootStage2} $out/init
-      substituteInPlace $out/init --subst-var-by systemConfig $out
+      ${if config.boot.initrd.systemd.enable then ''
+        cp ${config.system.build.bootStage2} $out/prepare-root
+        substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
+        # This must not be a symlink or the abs_path of the grub builder for the tests
+        # will resolve the symlink and we end up with a path that doesn't point to a
+        # system closure.
+        cp "$systemd/lib/systemd/systemd" $out/init
+      '' else ''
+        cp ${config.system.build.bootStage2} $out/init
+        substituteInPlace $out/init --subst-var-by systemConfig $out
+      ''}
 
       ln -s ${config.system.build.etc}/etc $out/etc
       ln -s ${config.system.path} $out/sw
@@ -156,7 +163,7 @@ in
 
     specialisation = mkOption {
       default = {};
-      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }";
+      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }; }";
       description = ''
         Additional configurations to build. If
         <literal>inheritParentConfig</literal> is true, the system
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index db00244ca0a..fad00e39497 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -241,7 +241,7 @@ in
             "xhci_pci"
             "usbhid"
             "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
-            "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft"
+            "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft" "hid_cherry"
 
           ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
             # Misc. x86 keyboard stuff.
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 8db271f8713..1f915d1f419 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -44,6 +44,8 @@ let
     { splashImage = f cfg.splashImage;
       splashMode = f cfg.splashMode;
       backgroundColor = f cfg.backgroundColor;
+      entryOptions = f cfg.entryOptions;
+      subEntryOptions = f cfg.subEntryOptions;
       grub = f grub;
       grubTarget = f (grub.grubTarget or "");
       shell = "${pkgs.runtimeShell}";
@@ -448,6 +450,30 @@ in
         '';
       };
 
+      entryOptions = mkOption {
+        default = "--class nixos --unrestricted";
+        type = types.nullOr types.str;
+        description = ''
+          Options applied to the primary NixOS menu entry.
+
+          <note><para>
+          This options has no effect for GRUB 1.
+          </para></note>
+        '';
+      };
+
+      subEntryOptions = mkOption {
+        default = "--class nixos";
+        type = types.nullOr types.str;
+        description = ''
+          Options applied to the secondary NixOS submenu entry.
+
+          <note><para>
+          This options has no effect for GRUB 1.
+          </para></note>
+        '';
+      };
+
       theme = mkOption {
         type = types.nullOr types.path;
         example = literalExpression "pkgs.nixos-grub2-theme";
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index 0c93b288fc6..d5f019423b6 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -64,6 +64,8 @@ my $extraEntries = get("extraEntries");
 my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true";
 my $splashImage = get("splashImage");
 my $splashMode = get("splashMode");
+my $entryOptions = get("entryOptions");
+my $subEntryOptions = get("subEntryOptions");
 my $backgroundColor = get("backgroundColor");
 my $configurationLimit = int(get("configurationLimit"));
 my $copyKernels = get("copyKernels") eq "true";
@@ -509,7 +511,7 @@ sub addEntry {
 # Add default entries.
 $conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
 
-addEntry("NixOS - Default", $defaultConfig, "--unrestricted");
+addEntry("NixOS - Default", $defaultConfig, $entryOptions);
 
 $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
 
@@ -546,7 +548,7 @@ sub addProfile {
     my ($profile, $description) = @_;
 
     # Add entries for all generations of this profile.
-    $conf .= "submenu \"$description\" {\n" if $grubVersion == 2;
+    $conf .= "submenu \"$description\" --class submenu {\n" if $grubVersion == 2;
 
     sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
 
@@ -566,7 +568,7 @@ sub addProfile {
             -e "$link/nixos-version"
             ? readFile("$link/nixos-version")
             : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
-        addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
+        addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions);
     }
 
     $conf .= "}\n" if $grubVersion == 2;
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index f0d3170dc5a..57fc02a2e32 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 
 let
   luks = config.boot.initrd.luks;
   kernelPackages = config.boot.kernelPackages;
+  defaultPrio = (mkOptionDefault {}).priority;
 
   commonFunctions = ''
     die() {
@@ -474,6 +475,16 @@ let
   preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
   postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
 
+  stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
+    opts = v.crypttabExtraOpts
+      ++ optional v.allowDiscards "discard"
+      ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
+      ++ optional (v.header != null) "header=${v.header}"
+      ++ optional (v.keyFileOffset != null) "keyfile-offset=${v.keyFileOffset}"
+      ++ optional (v.keyFileSize != null) "keyfile-size=${v.keyFileSize}"
+    ;
+  in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
+
 in
 {
   imports = [
@@ -802,6 +813,18 @@ in
               Commands that should be run right after we have mounted our LUKS device.
             '';
           };
+
+          crypttabExtraOpts = mkOption {
+            type = with types; listOf singleLineStr;
+            default = [];
+            example = [ "_netdev" ];
+            visible = false;
+            description = ''
+              Only used with systemd stage 1.
+
+              Extra options to append to the last column of the generated crypttab file.
+            '';
+          };
         };
       }));
     };
@@ -853,6 +876,31 @@ in
                       -> versionAtLeast kernelPackages.kernel.version "5.9";
           message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
         }
+
+        { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
+          message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
+        }
+        { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices);
+          message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.";
+        }
+        { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio;
+          message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1.";
+        }
+        { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices);
+          message = "boot.initrd.luks.devices.<name>.preOpenCommands and postOpenCommands is not supported by systemd stage 1. Please bind a service to cryptsetup.target or cryptsetup-pre.target instead.";
+        }
+        # TODO
+        { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
+          message = "systemd stage 1 does not support GPG smartcards yet.";
+        }
+        # TODO
+        { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
+          message = "systemd stage 1 does not support FIDO2 yet.";
+        }
+        # TODO
+        { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
+          message = "systemd stage 1 does not support Yubikeys yet.";
+        }
       ];
 
     # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
@@ -867,7 +915,7 @@ in
       ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
 
     # copy the cryptsetup binary and it's dependencies
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
       copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
       sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
@@ -877,7 +925,7 @@ in
         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
         copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
 
-        cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
+        cc -O3 -I${pkgs.openssl.dev}/include -L${lib.getLib pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
         strip -s pbkdf2-sha512
         copy_bin_and_libs pbkdf2-sha512
 
@@ -915,7 +963,7 @@ in
       ''}
     '';
 
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       $out/bin/cryptsetup --version
       ${optionalString luks.yubikeySupport ''
         $out/bin/ykchalresp -V
@@ -932,9 +980,27 @@ in
       ''}
     '';
 
-    boot.initrd.preFailCommands = postCommands;
-    boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
-    boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
+    boot.initrd.systemd = {
+      contents."/etc/crypttab".source = stage1Crypttab;
+
+      extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup";
+
+      additionalUpstreamUnits = [
+        "cryptsetup-pre.target"
+        "cryptsetup.target"
+        "remote-cryptsetup.target"
+      ];
+      storePaths = [
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"
+      ];
+
+    };
+    # We do this because we need the udev rules from the package
+    boot.initrd.services.lvm.enable = true;
+
+    boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands;
+    boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
+    boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
 
     environment.systemPackages = [ pkgs.cryptsetup ];
   };
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index ac1e4ef34b4..d1a6f46bfc4 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -10,6 +10,36 @@ let
 
   check = {
 
+    global = {
+      sectionNetwork = checkUnitConfig "Network" [
+        (assertOnlyFields [
+          "SpeedMeter"
+          "SpeedMeterIntervalSec"
+          "ManageForeignRoutingPolicyRules"
+          "ManageForeignRoutes"
+          "RouteTable"
+        ])
+        (assertValueOneOf "SpeedMeter" boolValues)
+        (assertInt "SpeedMeterIntervalSec")
+        (assertValueOneOf "ManageForeignRoutingPolicyRules" boolValues)
+        (assertValueOneOf "ManageForeignRoutes" boolValues)
+      ];
+
+      sectionDHCPv4 = checkUnitConfig "DHCPv4" [
+        (assertOnlyFields [
+          "DUIDType"
+          "DUIDRawData"
+        ])
+      ];
+
+      sectionDHCPv6 = checkUnitConfig "DHCPv6" [
+        (assertOnlyFields [
+          "DUIDType"
+          "DUIDRawData"
+        ])
+      ];
+    };
+
     link = {
 
       sectionLink = checkUnitConfig "Link" [
@@ -281,6 +311,8 @@ let
           "PrivateKeyFile"
           "ListenPort"
           "FirewallMark"
+          "RouteTable"
+          "RouteMetric"
         ])
         (assertInt "FirewallMark")
         (assertRange "FirewallMark" 1 4294967295)
@@ -296,6 +328,8 @@ let
           "AllowedIPs"
           "Endpoint"
           "PersistentKeepalive"
+          "RouteTable"
+          "RouteMetric"
         ])
         (assertInt "PersistentKeepalive")
         (assertRange "PersistentKeepalive" 0 65535)
@@ -867,6 +901,44 @@ let
     };
   };
 
+  networkdOptions = {
+    networkConfig = mkOption {
+      default = {};
+      example = { SpeedMeter = true; ManageForeignRoutingPolicyRules = false; };
+      type = types.addCheck (types.attrsOf unitOption) check.global.sectionNetwork;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Network]</literal> section of the networkd config.
+        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpV4Config = mkOption {
+      default = {};
+      example = { DUIDType = "vendor"; };
+      type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv4;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv4]</literal> section of the networkd config.
+        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpV6Config = mkOption {
+      default = {};
+      example = { DUIDType = "vendor"; };
+      type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv6;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCPv6]</literal> section of the networkd config.
+        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+  };
+
   linkOptions = commonNetworkOptions // {
     # overwrite enable option from above
     enable = mkOption {
@@ -1515,6 +1587,39 @@ let
     };
   };
 
+  networkdConfig = { config, ... }: {
+    options = {
+      routeTables = mkOption {
+        default = {};
+        example = { foo = 27; };
+        type = with types; attrsOf int;
+        description = ''
+          Defines route table names as an attrset of name to number.
+          See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+
+      addRouteTablesToIPRoute2 = mkOption {
+        default = true;
+        example = false;
+        type = types.bool;
+        description = ''
+          If true and routeTables are set, then the specified route tables
+          will also be installed into /etc/iproute2/rt_tables.
+        '';
+      };
+    };
+
+    config = {
+      networkConfig = optionalAttrs (config.routeTables != { }) {
+        RouteTable = mapAttrsToList
+          (name: number: "${name}:${toString number}")
+          config.routeTables;
+      };
+    };
+  };
+
   commonMatchText = def: optionalString (def.matchConfig != { }) ''
     [Match]
     ${attrsToSection def.matchConfig}
@@ -1596,6 +1701,20 @@ let
         + def.extraConfig;
     };
 
+  renderConfig = def:
+    { text = ''
+        [Network]
+        ${attrsToSection def.networkConfig}
+      ''
+      + optionalString (def.dhcpV4Config != { }) ''
+        [DHCPv4]
+        ${attrsToSection def.dhcpV4Config}
+      ''
+      + optionalString (def.dhcpV6Config != { }) ''
+        [DHCPv6]
+        ${attrsToSection def.dhcpV6Config}
+      ''; };
+
   networkToUnit = name: def:
     { inherit (def) enable;
       text = commonMatchText def
@@ -1728,6 +1847,12 @@ in
       description = "Definition of systemd networks.";
     };
 
+    systemd.network.config = mkOption {
+      default = {};
+      type = with types; submodule [ { options = networkdOptions; } networkdConfig ];
+      description = "Definition of global systemd network config.";
+    };
+
     systemd.network.units = mkOption {
       description = "Definition of networkd units.";
       default = {};
@@ -1741,6 +1866,48 @@ in
         }));
     };
 
+    systemd.network.wait-online = {
+      anyInterface = mkOption {
+        description = ''
+          Whether to consider the network online when any interface is online, as opposed to all of them.
+          This is useful on portable machines with a wired and a wireless interface, for example.
+        '';
+        type = types.bool;
+        default = false;
+      };
+
+      ignoredInterfaces = mkOption {
+        description = ''
+          Network interfaces to be ignored when deciding if the system is online.
+        '';
+        type = with types; listOf str;
+        default = [];
+        example = [ "wg0" ];
+      };
+
+      timeout = mkOption {
+        description = ''
+          Time to wait for the network to come online, in seconds. Set to 0 to disable.
+        '';
+        type = types.ints.unsigned;
+        default = 120;
+        example = 0;
+      };
+
+      extraArgs = mkOption {
+        description = ''
+          Extra command-line arguments to pass to systemd-networkd-wait-online.
+          These also affect per-interface <literal>systemd-network-wait-online@</literal> services.
+
+          See <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html">
+          <citerefentry><refentrytitle>systemd-networkd-wait-online.service</refentrytitle><manvolnum>8</manvolnum>
+          </citerefentry></link> for all available options.
+        '';
+        type = with types; listOf str;
+        default = [];
+      };
+    };
+
   };
 
   config = mkMerge [
@@ -1749,6 +1916,11 @@ in
     {
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
       environment.etc = unitFiles;
+
+      systemd.network.wait-online.extraArgs =
+        [ "--timeout=${toString cfg.wait-online.timeout}" ]
+        ++ optional cfg.wait-online.anyInterface "--any"
+        ++ map (i: "--ignore=${i}") cfg.wait-online.ignoredInterfaces;
     }
 
     (mkIf config.systemd.network.enable {
@@ -1772,11 +1944,17 @@ in
       systemd.services.systemd-networkd = {
         wantedBy = [ "multi-user.target" ];
         aliases = [ "dbus-org.freedesktop.network1.service" ];
-        restartTriggers = map (x: x.source) (attrValues unitFiles);
+        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
+          config.environment.etc."systemd/networkd.conf".source
+        ];
       };
 
       systemd.services.systemd-networkd-wait-online = {
         wantedBy = [ "network-online.target" ];
+        serviceConfig.ExecStart = [
+          ""
+          "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}"
+        ];
       };
 
       systemd.services."systemd-network-wait-online@" = {
@@ -1787,10 +1965,21 @@ in
         serviceConfig = {
           Type = "oneshot";
           RemainAfterExit = true;
-          ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+          ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}";
         };
       };
 
+      environment.etc."systemd/networkd.conf" = renderConfig cfg.config;
+
+      networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) {
+        enable = mkDefault true;
+        rttablesExtraConfig = ''
+
+          # Extra tables defined in NixOS systemd.networkd.config.routeTables.
+          ${concatStringsSep "\n" (mapAttrsToList (name: number: "${toString number} ${name}") cfg.config.routeTables)}
+        '';
+      };
+
       services.resolved.enable = mkDefault true;
     })
   ];
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 8fcc1f02972..31758366980 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -232,7 +232,8 @@ done
 mkdir -p /lib
 ln -s @modulesClosure@/lib/modules /lib/modules
 ln -s @modulesClosure@/lib/firmware /lib/firmware
-echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
+# see comment in stage-1.nix for explanation
+echo @extraUtils@/bin/modprobe-kernel > /proc/sys/kernel/modprobe
 for i in @kernelModules@; do
     info "loading module $(basename $i)..."
     modprobe $i
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 8b011d91563..d10ebac5682 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -131,6 +131,26 @@ let
       copy_bin_and_libs ${pkgs.kmod}/bin/kmod
       ln -sf kmod $out/bin/modprobe
 
+      # Dirty hack to make sure the kernel properly loads modules
+      # such as ext4 on demand (e.g. on a `mount(2)` syscall). This is necessary
+      # because `kmod` isn't linked against `libpthread.so.0` anymore (since
+      # it was merged into `libc.so.6` since version `2.34`), but still needs
+      # to access it for some reason. This is not an issue in stage-1 itself
+      # because of the `LD_LIBRARY_PATH`-variable and anytime later because the rpath of
+      # kmod/modprobe points to glibc's `$out/lib` where `libpthread.so.6` exists.
+      # However, this is a problem when the kernel calls `modprobe` inside
+      # the initial ramdisk because it doesn't know about the
+      # `LD_LIBRARY_PATH` and the rpath was nuked.
+      #
+      # Also, we can't use `makeWrapper` here because `kmod` only does
+      # `modprobe` functionality if `argv[0] == "modprobe"`.
+      cat >$out/bin/modprobe-kernel <<EOF
+      #!$out/bin/ash
+      export LD_LIBRARY_PATH=$out/lib
+      exec $out/bin/modprobe "\$@"
+      EOF
+      chmod +x $out/bin/modprobe-kernel
+
       # Copy resize2fs if any ext* filesystems are to be resized
       ${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
         # We need mke2fs in the initrd.
@@ -335,7 +355,7 @@ let
       [ { object = bootStage1;
           symlink = "/init";
         }
-        { object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf;
+        { object = pkgs.writeText "mdadm.conf" config.boot.initrd.services.swraid.mdadmConf;
           symlink = "/etc/mdadm.conf";
         }
         { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
@@ -400,7 +420,7 @@ let
         ${lib.optionalString (config.boot.initrd.secrets == {})
             "exit 0"}
 
-        export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
+        export PATH=${pkgs.coreutils}/bin:${pkgs.libarchive}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
 
         function cleanup {
           if [ -n "$tmp" -a -d "$tmp" ]; then
@@ -420,7 +440,7 @@ let
           ) config.boot.initrd.secrets)
          }
 
-        (cd "$tmp" && find . -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null) | \
+        (cd "$tmp" && find . -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
           ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
       '';
 
@@ -485,14 +505,6 @@ in
       '';
     };
 
-    boot.initrd.mdadmConf = mkOption {
-      default = "";
-      type = types.lines;
-      description = ''
-        Contents of <filename>/etc/mdadm.conf</filename> in stage 1.
-      '';
-    };
-
     boot.initrd.preLVMCommands = mkOption {
       default = "";
       type = types.lines;
@@ -703,8 +715,12 @@ in
       }
     ];
 
-    system.build =
-      { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; };
+    system.build = mkMerge [
+      { inherit bootStage1 initialRamdiskSecretAppender extraUtils; }
+
+      # generated in nixos/modules/system/boot/systemd/initrd.nix
+      (mkIf (!config.boot.initrd.systemd.enable) { inherit initialRamdisk; })
+    ];
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isYes "TMPFS")
@@ -712,6 +728,9 @@ in
     ];
 
     boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems;
-
   };
+
+  imports = [
+    (mkRenamedOptionModule [ "boot" "initrd" "mdadmConf" ] [ "boot" "initrd" "services" "swraid" "mdadmConf" ])
+  ];
 }
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index a90f58042d2..f2a839d0786 100755
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -5,32 +5,30 @@ systemConfig=@systemConfig@
 export HOME=/root PATH="@path@"
 
 
-# Process the kernel command line.
-for o in $(</proc/cmdline); do
-    case $o in
-        boot.debugtrace)
-            # Show each command.
-            set -x
-            ;;
-        resume=*)
-            set -- $(IFS==; echo $o)
-            resumeDevice=$2
-            ;;
-    esac
-done
-
-
-# Print a greeting.
-echo
-echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
-echo
-
-
-# Normally, stage 1 mounts the root filesystem read/writable.
-# However, in some environments, stage 2 is executed directly, and the
-# root is read-only.  So make it writable here.
-if [ -z "$container" ]; then
-    mount -n -o remount,rw none /
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
+    # Process the kernel command line.
+    for o in $(</proc/cmdline); do
+        case $o in
+            boot.debugtrace)
+                # Show each command.
+                set -x
+                ;;
+        esac
+    done
+
+
+    # Print a greeting.
+    echo
+    echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
+    echo
+
+
+    # Normally, stage 1 mounts the root filesystem read/writable.
+    # However, in some environments, stage 2 is executed directly, and the
+    # root is read-only.  So make it writable here.
+    if [ -z "$container" ]; then
+        mount -n -o remount,rw none /
+    fi
 fi
 
 
@@ -43,6 +41,12 @@ if [ ! -e /proc/1 ]; then
         local options="$3"
         local fsType="$4"
 
+        # We must not overwrite this mount because it's bind-mounted
+        # from stage 1's /run
+        if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] && [ "${mountPoint}" = /run ]; then
+            return
+        fi
+
         install -m 0755 -d "$mountPoint"
         mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
     }
@@ -50,7 +54,11 @@ if [ ! -e /proc/1 ]; then
 fi
 
 
-echo "booting system configuration $systemConfig" > /dev/kmsg
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then
+    echo "booting system configuration ${systemConfig}"
+else
+    echo "booting system configuration $systemConfig" > /dev/kmsg
+fi
 
 
 # Make /nix/store a read-only bind mount to enforce immutability of
@@ -72,59 +80,32 @@ if [ -n "@readOnlyStore@" ]; then
 fi
 
 
-# Provide a /etc/mtab.
-install -m 0755 -d /etc
-test -e /etc/fstab || touch /etc/fstab # to shut up mount
-rm -f /etc/mtab* # not that we care about stale locks
-ln -s /proc/mounts /etc/mtab
-
-
-# More special file systems, initialise required directories.
-[ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default
-install -m 01777 -d /tmp
-install -m 0755 -d /var/{log,lib,db} /nix/var /etc/nixos/ \
-    /run/lock /home /bin # for the /bin/sh symlink
-
-
-# Miscellaneous boot time cleanup.
-rm -rf /var/run /var/lock
-rm -f /etc/{group,passwd,shadow}.lock
-
-
-# Also get rid of temporary GC roots.
-rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
-
-
-# For backwards compatibility, symlink /var/run to /run, and /var/lock
-# to /run/lock.
-ln -s /run /var/run
-ln -s /run/lock /var/lock
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
+    # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+    if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
+        resolvconf -m 1000 -a host </etc/resolv.conf
+    fi
 
 
-# Clear the resume device.
-if test -n "$resumeDevice"; then
-    mkswap "$resumeDevice" || echo 'Failed to clear saved image.'
+    # Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
+    # Only at this point are all the necessary prerequisites ready for these commands.
+    exec {logOutFd}>&1 {logErrFd}>&2
+    if test -w /dev/kmsg; then
+        exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do
+            if test -n "$line"; then
+                echo "<7>stage-2-init: $line" > /dev/kmsg
+            fi
+        done) 2>&1
+    else
+        mkdir -p /run/log
+        exec > >(tee -i /run/log/stage-2-init.log) 2>&1
+    fi
 fi
 
 
-# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
-if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
-    resolvconf -m 1000 -a host </etc/resolv.conf
-fi
-
-# Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
-# Only at this point are all the necessary prerequisites ready for these commands.
-exec {logOutFd}>&1 {logErrFd}>&2
-if test -w /dev/kmsg; then
-    exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do
-        if test -n "$line"; then
-            echo "<7>stage-2-init: $line" > /dev/kmsg
-        fi
-    done) 2>&1
-else
-    mkdir -p /run/log
-    exec > >(tee -i /run/log/stage-2-init.log) 2>&1
-fi
+# Required by the activation script
+install -m 0755 -d /etc /etc/nixos
+install -m 01777 -d /tmp
 
 
 # Run the script that performs all configuration activation that does
@@ -133,22 +114,9 @@ echo "running activation script..."
 $systemConfig/activate
 
 
-# Restore the system time from the hardware clock.  We do this after
-# running the activation script to be sure that /etc/localtime points
-# at the current time zone.
-if [ -e /dev/rtc ]; then
-    hwclock --hctosys
-fi
-
-
 # Record the boot configuration.
 ln -sfn "$systemConfig" /run/booted-system
 
-# Prevent the booted system from being garbage-collected. If it weren't
-# a gcroot, if we were running a different kernel, switched system,
-# and garbage collected all, we could not load kernel modules anymore.
-ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
-
 
 # Run any user-specified commands.
 @shell@ @postBootCommands@
@@ -162,15 +130,15 @@ ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
 : >> /etc/machine-id
 
 
-# Reset the logging file descriptors.
-exec 1>&$logOutFd 2>&$logErrFd
-exec {logOutFd}>&- {logErrFd}>&-
-
+# No need to restore the stdout/stderr streams we never redirected and
+# especially no need to start systemd
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
+    # Reset the logging file descriptors.
+    exec 1>&$logOutFd 2>&$logErrFd
+    exec {logOutFd}>&- {logErrFd}>&-
 
-# Start systemd.
-echo "starting systemd..."
 
-PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
-    LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive @systemdUnitPathEnvVar@ \
-    TZDIR=/etc/zoneinfo \
-    exec @systemdExecutable@
+    # Start systemd in a clean environment.
+    echo "starting systemd..."
+    exec @systemdExecutable@ "$@"
+fi
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index f6b6a8e4b0b..f6461daf311 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -19,11 +19,6 @@ let
       pkgs.coreutils
       pkgs.util-linux
     ] ++ lib.optional useHostResolvConf pkgs.openresolv);
-    fsPackagesPath = lib.makeBinPath config.system.fsPackages;
-    systemdUnitPathEnvVar = lib.optionalString (config.boot.extraSystemdUnitPaths != [])
-      ("SYSTEMD_UNIT_PATH="
-      + builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths
-      + ":"); # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
     postBootCommands = pkgs.writeText "local-cmds"
       ''
         ${config.boot.postBootCommands}
@@ -47,43 +42,11 @@ in
         '';
       };
 
-      devSize = mkOption {
-        default = "5%";
-        example = "32m";
-        type = types.str;
-        description = ''
-          Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
-      devShmSize = mkOption {
-        default = "50%";
-        example = "256m";
-        type = types.str;
-        description = ''
-          Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
-      runSize = mkOption {
-        default = "25%";
-        example = "256m";
-        type = types.str;
-        description = ''
-          Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
       systemdExecutable = mkOption {
-        default = "systemd";
+        default = "/run/current-system/systemd/lib/systemd/systemd";
         type = types.str;
         description = ''
-          The program to execute to start systemd. Typically
-          <literal>systemd</literal>, which will find systemd in the
-          PATH.
+          The program to execute to start systemd.
         '';
       };
 
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index ff002d87a12..2c9ee9fc319 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -2,7 +2,6 @@
 
 with utils;
 with systemdUtils.unitOptions;
-with systemdUtils.lib;
 with lib;
 
 let
@@ -12,12 +11,7 @@ let
   systemd = cfg.package;
 
   inherit (systemdUtils.lib)
-    makeJobScript
-    unitConfig
-    serviceConfig
-    mountConfig
-    automountConfig
-    commonUnitText
+    generateUnits
     targetToUnit
     serviceToUnit
     socketToUnit
@@ -79,32 +73,6 @@ let
       "printer.target"
       "smartcard.target"
 
-      # Login stuff.
-      "systemd-logind.service"
-      "autovt@.service"
-      "systemd-user-sessions.service"
-      "dbus-org.freedesktop.import1.service"
-      "dbus-org.freedesktop.machine1.service"
-      "dbus-org.freedesktop.login1.service"
-      "user@.service"
-      "user-runtime-dir@.service"
-
-      # Journal.
-      "systemd-journald.socket"
-      "systemd-journald@.socket"
-      "systemd-journald-varlink@.socket"
-      "systemd-journald.service"
-      "systemd-journald@.service"
-      "systemd-journal-flush.service"
-      "systemd-journal-catalog-update.service"
-      ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
-      "systemd-journald-dev-log.socket"
-      "syslog.socket"
-
-      # Coredumps.
-      "systemd-coredump.socket"
-      "systemd-coredump@.service"
-
       # Kernel module loading.
       "systemd-modules-load.service"
       "kmod-static-nodes.service"
@@ -165,19 +133,12 @@ let
 
       # Slices / containers.
       "slices.target"
-      "user.slice"
       "machine.slice"
       "machines.target"
       "systemd-importd.service"
       "systemd-machined.service"
       "systemd-nspawn@.service"
 
-      # Temporary file creation / cleanup.
-      "systemd-tmpfiles-clean.service"
-      "systemd-tmpfiles-clean.timer"
-      "systemd-tmpfiles-setup.service"
-      "systemd-tmpfiles-setup-dev.service"
-
       # Misc.
       "systemd-sysctl.service"
       "dbus-org.freedesktop.timedate1.service"
@@ -188,9 +149,6 @@ let
       "systemd-hostnamed.service"
       "systemd-exit.service"
       "systemd-update-done.service"
-    ] ++ optionals config.services.journald.enableHttpGateway [
-      "systemd-journal-gatewayd.socket"
-      "systemd-journal-gatewayd.service"
     ] ++ cfg.additionalUpstreamSystemUnits;
 
   upstreamSystemWants =
@@ -201,36 +159,6 @@ let
       "timers.target.wants"
     ];
 
-    upstreamUserUnits = [
-      "app.slice"
-      "background.slice"
-      "basic.target"
-      "bluetooth.target"
-      "default.target"
-      "exit.target"
-      "graphical-session-pre.target"
-      "graphical-session.target"
-      "paths.target"
-      "printer.target"
-      "session.slice"
-      "shutdown.target"
-      "smartcard.target"
-      "sockets.target"
-      "sound.target"
-      "systemd-exit.service"
-      "systemd-tmpfiles-clean.service"
-      "systemd-tmpfiles-clean.timer"
-      "systemd-tmpfiles-setup.service"
-      "timers.target"
-      "xdg-desktop-autostart.target"
-    ];
-
-
-  logindHandlerType = types.enum [
-    "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
-    "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
-  ];
-
   proxy_env = config.networking.proxy.envVars;
 
 in
@@ -250,13 +178,7 @@ in
     systemd.units = mkOption {
       description = "Definition of systemd units.";
       default = {};
-      type = with types; attrsOf (submodule (
-        { name, config, ... }:
-        { options = concreteUnitOptions;
-          config = {
-            unit = mkDefault (makeUnit name config);
-          };
-        }));
+      type = systemdUtils.types.units;
     };
 
     systemd.packages = mkOption {
@@ -268,37 +190,37 @@ in
 
     systemd.targets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+      type = systemdUtils.types.targets;
       description = "Definition of systemd target units.";
     };
 
     systemd.services = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
+      type = systemdUtils.types.services;
       description = "Definition of systemd service units.";
     };
 
     systemd.sockets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
+      type = systemdUtils.types.sockets;
       description = "Definition of systemd socket units.";
     };
 
     systemd.timers = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
+      type = systemdUtils.types.timers;
       description = "Definition of systemd timer units.";
     };
 
     systemd.paths = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+      type = systemdUtils.types.paths;
       description = "Definition of systemd path units.";
     };
 
     systemd.mounts = mkOption {
       default = [];
-      type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
+      type = systemdUtils.types.mounts;
       description = ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -308,7 +230,7 @@ in
 
     systemd.automounts = mkOption {
       default = [];
-      type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
+      type = systemdUtils.types.automounts;
       description = ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -318,7 +240,7 @@ in
 
     systemd.slices = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
+      type = systemdUtils.types.slices;
       description = "Definition of slice configurations.";
     };
 
@@ -367,39 +289,29 @@ in
       '';
     };
 
-    systemd.enableCgroupAccounting = mkOption {
-      default = true;
-      type = types.bool;
+    systemd.managerEnvironment = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = {};
+      example = { SYSTEMD_LOG_LEVEL = "debug"; };
       description = ''
-        Whether to enable cgroup accounting.
+        Environment variables of PID 1. These variables are
+        <emphasis>not</emphasis> passed to started units.
       '';
     };
 
-    systemd.enableUnifiedCgroupHierarchy = mkOption {
+    systemd.enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
       description = ''
-        Whether to enable the unified cgroup hierarchy (cgroupsv2).
+        Whether to enable cgroup accounting.
       '';
     };
 
-    systemd.coredump.enable = mkOption {
+    systemd.enableUnifiedCgroupHierarchy = mkOption {
       default = true;
       type = types.bool;
       description = ''
-        Whether core dumps should be processed by
-        <command>systemd-coredump</command>. If disabled, core dumps
-        appear in the current directory of the crashing process.
-      '';
-    };
-
-    systemd.coredump.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      example = "Storage=journal";
-      description = ''
-        Extra config options for systemd-coredump. See coredump.conf(5) man page
-        for available options.
+        Whether to enable the unified cgroup hierarchy (cgroupsv2).
       '';
     };
 
@@ -413,142 +325,6 @@ in
       '';
     };
 
-    services.journald.console = mkOption {
-      default = "";
-      type = types.str;
-      description = "If non-empty, write log messages to the specified TTY device.";
-    };
-
-    services.journald.rateLimitInterval = mkOption {
-      default = "30s";
-      type = types.str;
-      description = ''
-        Configures the rate limiting interval that is applied to all
-        messages generated on the system. This rate limiting is applied
-        per-service, so that two services which log do not interfere with
-        each other's limit. The value may be specified in the following
-        units: s, min, h, ms, us. To turn off any kind of rate limiting,
-        set either value to 0.
-
-        See <option>services.journald.rateLimitBurst</option> for important
-        considerations when setting this value.
-      '';
-    };
-
-    services.journald.rateLimitBurst = mkOption {
-      default = 10000;
-      type = types.int;
-      description = ''
-        Configures the rate limiting burst limit (number of messages per
-        interval) that is applied to all messages generated on the system.
-        This rate limiting is applied per-service, so that two services
-        which log do not interfere with each other's limit.
-
-        Note that the effective rate limit is multiplied by a factor derived
-        from the available free disk space for the journal as described on
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
-        journald.conf(5)</link>.
-
-        Note that the total amount of logs stored is limited by journald settings
-        such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
-
-        It is thus recommended to compute what period of time that you will be
-        able to store logs for when an application logs at full burst rate.
-        With default settings for log lines that are 100 Bytes long, this can
-        amount to just a few hours.
-      '';
-    };
-
-    services.journald.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      example = "Storage=volatile";
-      description = ''
-        Extra config options for systemd-journald. See man journald.conf
-        for available options.
-      '';
-    };
-
-    services.journald.enableHttpGateway = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Whether to enable the HTTP gateway to the journal.
-      '';
-    };
-
-    services.journald.forwardToSyslog = mkOption {
-      default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
-      defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
-      type = types.bool;
-      description = ''
-        Whether to forward log messages to syslog.
-      '';
-    };
-
-    services.logind.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      example = "IdleAction=lock";
-      description = ''
-        Extra config options for systemd-logind. See
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
-        logind.conf(5)</link> for available options.
-      '';
-    };
-
-    services.logind.killUserProcesses = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Specifies whether the processes of a user should be killed
-        when the user logs out.  If true, the scope unit corresponding
-        to the session and all processes inside that scope will be
-        terminated.  If false, the scope is "abandoned" (see
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
-        systemd.scope(5)</link>), and processes are not killed.
-        </para>
-
-        <para>
-        See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
-        for more details.
-      '';
-    };
-
-    services.logind.lidSwitch = mkOption {
-      default = "suspend";
-      example = "ignore";
-      type = logindHandlerType;
-
-      description = ''
-        Specifies what to be done when the laptop lid is closed.
-      '';
-    };
-
-    services.logind.lidSwitchDocked = mkOption {
-      default = "ignore";
-      example = "suspend";
-      type = logindHandlerType;
-
-      description = ''
-        Specifies what to be done when the laptop lid is closed
-        and another screen is added.
-      '';
-    };
-
-    services.logind.lidSwitchExternalPower = mkOption {
-      default = config.services.logind.lidSwitch;
-      defaultText = literalExpression "services.logind.lidSwitch";
-      example = "ignore";
-      type = logindHandlerType;
-
-      description = ''
-        Specifies what to do when the laptop lid is closed and the system is
-        on external power. By default use the same action as specified in
-        services.logind.lidSwitch.
-      '';
-    };
-
     systemd.sleep.extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -559,95 +335,6 @@ in
       '';
     };
 
-    systemd.user.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      example = "DefaultCPUAccounting=yes";
-      description = ''
-        Extra config options for systemd user instances. See man systemd-user.conf for
-        available options.
-      '';
-    };
-
-    systemd.tmpfiles.rules = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      example = [ "d /tmp 1777 root root 10d" ];
-      description = ''
-        Rules for creation, deletion and cleaning of volatile and temporary files
-        automatically. See
-        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        for the exact format.
-      '';
-    };
-
-    systemd.tmpfiles.packages = mkOption {
-      type = types.listOf types.package;
-      default = [];
-      example = literalExpression "[ pkgs.lvm2 ]";
-      apply = map getLib;
-      description = ''
-        List of packages containing <command>systemd-tmpfiles</command> rules.
-
-        All files ending in .conf found in
-        <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
-        will be included.
-        If this folder does not exist or does not contain any files an error will be returned instead.
-
-        If a <filename>lib</filename> output is available, rules are searched there and only there.
-        If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
-        and if that does not exist either, the default output will be used.
-      '';
-    };
-
-    systemd.user.units = mkOption {
-      description = "Definition of systemd per-user units.";
-      default = {};
-      type = with types; attrsOf (submodule (
-        { name, config, ... }:
-        { options = concreteUnitOptions;
-          config = {
-            unit = mkDefault (makeUnit name config);
-          };
-        }));
-    };
-
-    systemd.user.paths = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
-      description = "Definition of systemd per-user path units.";
-    };
-
-    systemd.user.services = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
-      description = "Definition of systemd per-user service units.";
-    };
-
-    systemd.user.slices = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
-      description = "Definition of systemd per-user slice units.";
-    };
-
-    systemd.user.sockets = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
-      description = "Definition of systemd per-user socket units.";
-    };
-
-    systemd.user.targets = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
-      description = "Definition of systemd per-user target units.";
-    };
-
-    systemd.user.timers = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
-      description = "Definition of systemd per-user timer units.";
-    };
-
     systemd.additionalUpstreamSystemUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
@@ -662,10 +349,11 @@ in
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
       description = ''
-        A list of units to suppress when generating system systemd configuration directory. This has
+        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
-        suppress a upstream systemd unit with any modifications made to it by other NixOS modules.
+        prevent a upstream systemd unit from being added to the initrd with any modifications made to it
+        by other NixOS modules.
       '';
     };
 
@@ -780,13 +468,18 @@ in
 
       enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
       enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
-    in ({
-      "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants;
 
-      "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits [];
+    in ({
+      "systemd/system".source = generateUnits {
+        type = "system";
+        units = enabledUnits;
+        upstreamUnits = enabledUpstreamSystemUnits;
+        upstreamWants = upstreamSystemWants;
+      };
 
       "systemd/system.conf".text = ''
         [Manager]
+        ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         ${optionalString config.systemd.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
           DefaultIOAccounting=yes
@@ -810,76 +503,17 @@ in
         ${config.systemd.extraConfig}
       '';
 
-      "systemd/user.conf".text = ''
-        [Manager]
-        ${config.systemd.user.extraConfig}
-      '';
-
-      "systemd/journald.conf".text = ''
-        [Journal]
-        Storage=persistent
-        RateLimitInterval=${config.services.journald.rateLimitInterval}
-        RateLimitBurst=${toString config.services.journald.rateLimitBurst}
-        ${optionalString (config.services.journald.console != "") ''
-          ForwardToConsole=yes
-          TTYPath=${config.services.journald.console}
-        ''}
-        ${optionalString (config.services.journald.forwardToSyslog) ''
-          ForwardToSyslog=yes
-        ''}
-        ${config.services.journald.extraConfig}
-      '';
-
-      "systemd/coredump.conf".text =
-        ''
-          [Coredump]
-          ${config.systemd.coredump.extraConfig}
-        '';
-
-      "systemd/logind.conf".text = ''
-        [Login]
-        KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"}
-        HandleLidSwitch=${config.services.logind.lidSwitch}
-        HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked}
-        HandleLidSwitchExternalPower=${config.services.logind.lidSwitchExternalPower}
-        ${config.services.logind.extraConfig}
-      '';
-
       "systemd/sleep.conf".text = ''
         [Sleep]
         ${config.systemd.sleep.extraConfig}
       '';
 
-      # install provided sysctl snippets
-      "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
-      "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
-
-      "tmpfiles.d".source = (pkgs.symlinkJoin {
-        name = "tmpfiles.d";
-        paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.packages;
-        postBuild = ''
-          for i in $(cat $pathsPath); do
-            (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
-              echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
-              exit 1
-            )
-          done
-        '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
-          rm -f $out/${removePrefix "tmpfiles.d/" name}
-        '') config.system.build.etc.passthru.targets;
-      }) + "/*";
-
       "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
       "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
     });
 
     services.dbus.enable = true;
 
-    users.users.systemd-coredump = {
-      uid = config.ids.uids.systemd-coredump;
-      group = "systemd-coredump";
-    };
-    users.groups.systemd-coredump = {};
     users.users.systemd-network = {
       uid = config.ids.uids.systemd-network;
       group = "systemd-network";
@@ -899,36 +533,6 @@ in
         unitConfig.X-StopOnReconfiguration = true;
       };
 
-    systemd.tmpfiles.packages = [
-      # Default tmpfiles rules provided by systemd
-      (pkgs.runCommand "systemd-default-tmpfiles" {} ''
-        mkdir -p $out/lib/tmpfiles.d
-        cd $out/lib/tmpfiles.d
-
-        ln -s "${systemd}/example/tmpfiles.d/home.conf"
-        ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
-        ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
-        ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
-        ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
-        ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
-        ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
-        ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
-        ln -s "${systemd}/example/tmpfiles.d/var.conf"
-        ln -s "${systemd}/example/tmpfiles.d/x11.conf"
-      '')
-      # User-specified tmpfiles rules
-      (pkgs.writeTextFile {
-        name = "nixos-tmpfiles.d";
-        destination = "/lib/tmpfiles.d/00-nixos.conf";
-        text = ''
-          # This file is created automatically and should not be modified.
-          # Please change the option ‘systemd.tmpfiles.rules’ instead.
-
-          ${concatStringsSep "\n" cfg.tmpfiles.rules}
-        '';
-      })
-    ];
-
     systemd.units =
          mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
       // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
@@ -943,13 +547,16 @@ in
                    (v: let n = escapeSystemdPath v.where;
                        in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
-    systemd.user.units =
-         mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.user.paths
-      // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
-      // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.user.slices
-      // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.user.sockets
-      // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.user.targets
-      // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.user.timers;
+      # Environment of PID 1
+      systemd.managerEnvironment = {
+        # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools
+        PATH = lib.makeBinPath config.system.fsPackages;
+        LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
+        TZDIR = "/etc/zoneinfo";
+        # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
+        SYSTEMD_UNIT_PATH = lib.mkIf (config.boot.extraSystemdUnitPaths != []) "${builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths}:";
+      };
+
 
     system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
       [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
@@ -958,11 +565,6 @@ in
         "TMPFS_XATTR" "SECCOMP"
       ];
 
-    users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
-    users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
-    users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
-    users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
-
     # Generate timer units for all services that have a ‘startAt’ value.
     systemd.timers =
       mapAttrs (name: service:
@@ -971,50 +573,14 @@ in
         })
         (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
 
-    # Generate timer units for all services that have a ‘startAt’ value.
-    systemd.user.timers =
-      mapAttrs (name: service:
-        { wantedBy = [ "timers.target" ];
-          timerConfig.OnCalendar = service.startAt;
-        })
-        (filterAttrs (name: service: service.startAt != []) cfg.user.services);
-
-    systemd.sockets.systemd-journal-gatewayd.wantedBy =
-      optional config.services.journald.enableHttpGateway "sockets.target";
-
-    # Provide the systemd-user PAM service, required to run systemd
-    # user instances.
-    security.pam.services.systemd-user =
-      { # Ensure that pam_systemd gets included. This is special-cased
-        # in systemd to provide XDG_RUNTIME_DIR.
-        startSession = true;
-      };
-
     # Some overrides to upstream units.
     systemd.services."systemd-backlight@".restartIfChanged = false;
     systemd.services."systemd-fsck@".restartIfChanged = false;
     systemd.services."systemd-fsck@".path = [ config.system.path ];
-    systemd.services."user@".restartIfChanged = false;
-    systemd.services.systemd-journal-flush.restartIfChanged = false;
     systemd.services.systemd-random-seed.restartIfChanged = false;
     systemd.services.systemd-remount-fs.restartIfChanged = false;
     systemd.services.systemd-update-utmp.restartIfChanged = false;
-    systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
     systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
-    # Restarting systemd-logind breaks X11
-    # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
-    # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
-    # - this might be addressed in the future by xorg
-    #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
-    systemd.services.systemd-logind.restartIfChanged = false;
-    systemd.services.systemd-logind.stopIfChanged = false;
-    # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
-    systemd.services."user-runtime-dir@".stopIfChanged = false;
-    systemd.services."user-runtime-dir@".restartIfChanged = false;
-    systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
-    systemd.services.systemd-journald.stopIfChanged = false;
-    systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
-    systemd.services."systemd-journald@".stopIfChanged = false;
     systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
     systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
     systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
@@ -1025,30 +591,24 @@ in
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
     systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
 
-    boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.coredump.enable) "core";
-
     # Increase numeric PID range (set directly instead of copying a one-line file from systemd)
     # https://github.com/systemd/systemd/pull/12226
     boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
 
     boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
 
-    services.logrotate.paths = {
+    services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0660 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0660 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
       "/var/log/wtmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0664 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0664 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
     };
   };
diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix
new file mode 100644
index 00000000000..b6ee2cff1f9
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/coredump.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.systemd.coredump;
+  systemd = config.systemd.package;
+in {
+  options = {
+    systemd.coredump.enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether core dumps should be processed by
+        <command>systemd-coredump</command>. If disabled, core dumps
+        appear in the current directory of the crashing process.
+      '';
+    };
+
+    systemd.coredump.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "Storage=journal";
+      description = ''
+        Extra config options for systemd-coredump. See coredump.conf(5) man page
+        for available options.
+      '';
+    };
+  };
+
+  config = {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-coredump.socket"
+      "systemd-coredump@.service"
+    ];
+
+    environment.etc = {
+      "systemd/coredump.conf".text =
+      ''
+        [Coredump]
+        ${cfg.extraConfig}
+      '';
+
+      # install provided sysctl snippets
+      "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+      "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
+    };
+
+    users.users.systemd-coredump = {
+      uid = config.ids.uids.systemd-coredump;
+      group = "systemd-coredump";
+    };
+    users.groups.systemd-coredump = {};
+
+    boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.enable) "core";
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
new file mode 100644
index 00000000000..fc4bd6ff69b
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -0,0 +1,509 @@
+{ lib, config, utils, pkgs, ... }:
+
+with lib;
+
+let
+  inherit (utils) systemdUtils escapeSystemdPath;
+  inherit (systemdUtils.lib)
+    generateUnits
+    pathToUnit
+    serviceToUnit
+    sliceToUnit
+    socketToUnit
+    targetToUnit
+    timerToUnit
+    mountToUnit
+    automountToUnit;
+
+
+  cfg = config.boot.initrd.systemd;
+
+  # Copied from fedora
+  upstreamUnits = [
+    "basic.target"
+    "ctrl-alt-del.target"
+    "emergency.service"
+    "emergency.target"
+    "final.target"
+    "halt.target"
+    "initrd-cleanup.service"
+    "initrd-fs.target"
+    "initrd-parse-etc.service"
+    "initrd-root-device.target"
+    "initrd-root-fs.target"
+    "initrd-switch-root.service"
+    "initrd-switch-root.target"
+    "initrd.target"
+    "kexec.target"
+    "kmod-static-nodes.service"
+    "local-fs-pre.target"
+    "local-fs.target"
+    "multi-user.target"
+    "paths.target"
+    "poweroff.target"
+    "reboot.target"
+    "rescue.service"
+    "rescue.target"
+    "rpcbind.target"
+    "shutdown.target"
+    "sigpwr.target"
+    "slices.target"
+    "sockets.target"
+    "swap.target"
+    "sysinit.target"
+    "sys-kernel-config.mount"
+    "syslog.socket"
+    "systemd-ask-password-console.path"
+    "systemd-ask-password-console.service"
+    "systemd-fsck@.service"
+    "systemd-halt.service"
+    "systemd-hibernate-resume@.service"
+    "systemd-journald-audit.socket"
+    "systemd-journald-dev-log.socket"
+    "systemd-journald.service"
+    "systemd-journald.socket"
+    "systemd-kexec.service"
+    "systemd-modules-load.service"
+    "systemd-poweroff.service"
+    "systemd-reboot.service"
+    "systemd-sysctl.service"
+    "systemd-tmpfiles-setup-dev.service"
+    "systemd-tmpfiles-setup.service"
+    "timers.target"
+    "umount.target"
+
+    # TODO: Networking
+    # "network-online.target"
+    # "network-pre.target"
+    # "network.target"
+    # "nss-lookup.target"
+    # "nss-user-lookup.target"
+    # "remote-fs-pre.target"
+    # "remote-fs.target"
+  ] ++ cfg.additionalUpstreamUnits;
+
+  upstreamWants = [
+    "sysinit.target.wants"
+  ];
+
+  enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits;
+  enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units;
+  jobScripts = concatLists (mapAttrsToList (_: unit: unit.jobScripts or []) (filterAttrs (_: v: v.enable) cfg.services));
+
+  stage1Units = generateUnits {
+    type = "initrd";
+    units = enabledUnits;
+    upstreamUnits = enabledUpstreamUnits;
+    inherit upstreamWants;
+    inherit (cfg) packages package;
+  };
+
+  fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
+
+  fstab = pkgs.writeText "initrd-fstab" (lib.concatMapStringsSep "\n"
+    ({ fsType, mountPoint, device, options, autoFormat, autoResize, ... }@fs: let
+        opts = options ++ optional autoFormat "x-systemd.makefs" ++ optional autoResize "x-systemd.growfs";
+      in "${device} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," opts}") fileSystems);
+
+  kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
+  modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
+  firmware = config.hardware.firmware;
+  # Determine the set of modules that we need to mount the root FS.
+  modulesClosure = pkgs.makeModulesClosure {
+    rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
+    kernel = modulesTree;
+    firmware = firmware;
+    allowMissing = false;
+  };
+
+  initrdBinEnv = pkgs.buildEnv {
+    name = "initrd-bin-env";
+    paths = map getBin cfg.initrdBin;
+    pathsToLink = ["/bin" "/sbin"];
+    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin);
+  };
+
+  initialRamdisk = pkgs.makeInitrdNG {
+    name = "initrd-${kernel-name}";
+    inherit (config.boot.initrd) compressor compressorArgs prepend;
+
+    contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
+      ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
+  };
+
+in {
+  options.boot.initrd.systemd = {
+    enable = mkEnableOption ''systemd in initrd.
+
+      Note: This is in very early development and is highly
+      experimental. Most of the features NixOS supports in initrd are
+      not yet supported by the intrd generated with this option.
+    '';
+
+    package = (mkPackageOption pkgs "systemd" {
+      default = "systemdStage1";
+    }) // {
+      visible = false;
+    };
+
+    contents = mkOption {
+      description = "Set of files that have to be linked into the initrd";
+      example = literalExpression ''
+        {
+          "/etc/hostname".text = "mymachine";
+        }
+      '';
+      visible = false;
+      default = {};
+      type = types.attrsOf (types.submodule ({ config, options, name, ... }: {
+        options = {
+          enable = mkEnableOption "copying of this file to initrd and symlinking it" // { default = true; };
+
+          target = mkOption {
+            type = types.path;
+            description = ''
+              Path of the symlink.
+            '';
+            default = name;
+          };
+
+          text = mkOption {
+            default = null;
+            type = types.nullOr types.lines;
+            description = "Text of the file.";
+          };
+
+          source = mkOption {
+            type = types.path;
+            description = "Path of the source file.";
+          };
+        };
+
+        config = {
+          source = mkIf (config.text != null) (
+            let name' = "initrd-" + baseNameOf name;
+            in mkDerivedConfig options.text (pkgs.writeText name')
+          );
+        };
+      }));
+    };
+
+    storePaths = mkOption {
+      description = ''
+        Store paths to copy into the initrd as well.
+      '';
+      type = with types; listOf (oneOf [ singleLineStr package ]);
+      default = [];
+    };
+
+    extraBin = mkOption {
+      description = ''
+        Tools to add to /bin
+      '';
+      example = literalExpression ''
+        {
+          umount = ''${pkgs.util-linux}/bin/umount;
+        }
+      '';
+      type = types.attrsOf types.path;
+      default = {};
+    };
+
+    suppressedStorePaths = mkOption {
+      description = ''
+        Store paths specified in the storePaths option that
+        should not be copied.
+      '';
+      type = types.listOf types.singleLineStr;
+      default = [];
+    };
+
+    emergencyAccess = mkOption {
+      type = with types; oneOf [ bool singleLineStr ];
+      visible = false;
+      description = ''
+        Set to true for unauthenticated emergency access, and false for
+        no emergency access.
+
+        Can also be set to a hashed super user password to allow
+        authenticated access to the emergency mode.
+      '';
+      default = false;
+    };
+
+    initrdBin = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      visible = false;
+      description = ''
+        Packages to include in /bin for the stage 1 emergency shell.
+      '';
+    };
+
+    additionalUpstreamUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      visible = false;
+      example = [ "debug-shell.service" "systemd-quotacheck.service" ];
+      description = ''
+        Additional units shipped with systemd that shall be enabled.
+      '';
+    };
+
+    suppressedUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      example = [ "systemd-backlight@.service" ];
+      visible = false;
+      description = ''
+        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
+        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.";
+      default = {};
+      visible = false;
+      type = systemdUtils.types.units;
+    };
+
+    packages = mkOption {
+      default = [];
+      visible = false;
+      type = types.listOf types.package;
+      example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
+      description = "Packages providing systemd units and hooks.";
+    };
+
+    targets = mkOption {
+      default = {};
+      visible = false;
+      type = systemdUtils.types.initrdTargets;
+      description = "Definition of systemd target units.";
+    };
+
+    services = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdServices;
+      visible = false;
+      description = "Definition of systemd service units.";
+    };
+
+    sockets = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdSockets;
+      visible = false;
+      description = "Definition of systemd socket units.";
+    };
+
+    timers = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdTimers;
+      visible = false;
+      description = "Definition of systemd timer units.";
+    };
+
+    paths = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdPaths;
+      visible = false;
+      description = "Definition of systemd path units.";
+    };
+
+    mounts = mkOption {
+      default = [];
+      type = systemdUtils.types.initrdMounts;
+      visible = false;
+      description = ''
+        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.
+      '';
+    };
+
+    automounts = mkOption {
+      default = [];
+      type = systemdUtils.types.automounts;
+      visible = false;
+      description = ''
+        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.
+      '';
+    };
+
+    slices = mkOption {
+      default = {};
+      type = systemdUtils.types.slices;
+      visible = false;
+      description = "Definition of slice configurations.";
+    };
+  };
+
+  config = mkIf (config.boot.initrd.enable && cfg.enable) {
+    system.build = { inherit initialRamdisk; };
+
+    boot.initrd.availableKernelModules = [ "autofs4" ]; # systemd needs this for some features
+
+    boot.initrd.systemd = {
+      initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
+      extraBin = {
+        less = "${pkgs.less}/bin/less";
+        mount = "${cfg.package.util-linux}/bin/mount";
+        umount = "${cfg.package.util-linux}/bin/umount";
+      };
+
+      contents = {
+        "/init".source = "${cfg.package}/lib/systemd/systemd";
+        "/etc/systemd/system".source = stage1Units;
+
+        "/etc/systemd/system.conf".text = ''
+          [Manager]
+          DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
+        '';
+
+        "/etc/fstab".source = fstab;
+
+        "/lib/modules".source = "${modulesClosure}/lib/modules";
+
+        "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
+
+        "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
+        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
+
+        "/bin".source = "${initrdBinEnv}/bin";
+        "/sbin".source = "${initrdBinEnv}/sbin";
+
+        "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe";
+        "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf";
+        "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } ''
+          ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
+        '';
+        "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
+
+      };
+
+      storePaths = [
+        # systemd tooling
+        "${cfg.package}/lib/systemd/systemd-fsck"
+        "${cfg.package}/lib/systemd/systemd-growfs"
+        "${cfg.package}/lib/systemd/systemd-hibernate-resume"
+        "${cfg.package}/lib/systemd/systemd-journald"
+        "${cfg.package}/lib/systemd/systemd-makefs"
+        "${cfg.package}/lib/systemd/systemd-modules-load"
+        "${cfg.package}/lib/systemd/systemd-remount-fs"
+        "${cfg.package}/lib/systemd/systemd-shutdown"
+        "${cfg.package}/lib/systemd/systemd-sulogin-shell"
+        "${cfg.package}/lib/systemd/systemd-sysctl"
+
+        # additional systemd directories
+        "${cfg.package}/lib/systemd/system-generators"
+
+        # utilities needed by systemd
+        "${cfg.package.util-linux}/bin/mount"
+        "${cfg.package.util-linux}/bin/umount"
+        "${cfg.package.util-linux}/bin/sulogin"
+
+        # so NSS can look up usernames
+        "${pkgs.glibc}/lib/libnss_files.so.2"
+      ] ++ jobScripts;
+
+      targets.initrd.aliases = ["default.target"];
+      units =
+           mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
+        // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
+        // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.slices
+        // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.sockets
+        // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.targets
+        // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.timers
+        // listToAttrs (map
+                     (v: let n = escapeSystemdPath v.where;
+                         in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
+        // listToAttrs (map
+                     (v: let n = escapeSystemdPath v.where;
+                         in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
+
+      # The unit in /run/systemd/generator shadows the unit in
+      # /etc/systemd/system, but will still apply drop-ins from
+      # /etc/systemd/system/foo.service.d/
+      #
+      # We need IgnoreOnIsolate, otherwise the Requires dependency of
+      # a mount unit on its makefs unit causes it to be unmounted when
+      # we isolate for switch-root. Use a dummy package so that
+      # generateUnits will generate drop-ins instead of unit files.
+      packages = [(pkgs.runCommand "dummy" {} ''
+        mkdir -p $out/etc/systemd/system
+        touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
+      '')];
+      services."systemd-makefs@".unitConfig.IgnoreOnIsolate = true;
+      services."systemd-growfs@".unitConfig.IgnoreOnIsolate = true;
+
+      services.initrd-nixos-activation = {
+        after = [ "initrd-fs.target" ];
+        requiredBy = [ "initrd.target" ];
+        unitConfig.AssertPathExists = "/etc/initrd-release";
+        serviceConfig.Type = "oneshot";
+        description = "NixOS Activation";
+
+        script = /* bash */ ''
+          set -uo pipefail
+          export PATH="/bin:${cfg.package.util-linux}/bin"
+
+          # Figure out what closure to boot
+          closure=
+          for o in $(< /proc/cmdline); do
+              case $o in
+                  init=*)
+                      IFS== read -r -a initParam <<< "$o"
+                      closure="$(dirname "''${initParam[1]}")"
+                      ;;
+              esac
+          done
+
+          # Sanity check
+          if [ -z "''${closure:-}" ]; then
+            echo 'No init= parameter on the kernel command line' >&2
+            exit 1
+          fi
+
+          # If we are not booting a NixOS closure (e.g. init=/bin/sh),
+          # we don't know what root to prepare so we don't do anything
+          if ! [ -x "/sysroot$closure/prepare-root" ]; then
+            echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
+            echo "$closure does not look like a NixOS installation - not activating"
+            exit 0
+          fi
+          echo 'NEW_INIT=' > /etc/switch-root.conf
+
+
+          # We need to propagate /run for things like /run/booted-system
+          # and /run/current-system.
+          mkdir -p /sysroot/run
+          mount --bind /run /sysroot/run
+
+          # Initialize the system
+          export IN_NIXOS_SYSTEMD_STAGE1=true
+          exec chroot /sysroot $closure/prepare-root
+        '';
+      };
+
+      # This will either call systemctl with the new init as the last parameter (which
+      # is the case when not booting a NixOS system) or with an empty string, causing
+      # systemd to bypass its verification code that checks whether the next file is a systemd
+      # and using its compiled-in value
+      services.initrd-switch-root.serviceConfig = {
+        EnvironmentFile = "-/etc/switch-root.conf";
+        ExecStart = [
+          ""
+          ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"''
+        ];
+      };
+    };
+
+    boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [ "resume=${config.boot.resumeDevice}" ];
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix
new file mode 100644
index 00000000000..7e14c8ae407
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/journald.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.journald;
+in {
+  options = {
+    services.journald.console = mkOption {
+      default = "";
+      type = types.str;
+      description = "If non-empty, write log messages to the specified TTY device.";
+    };
+
+    services.journald.rateLimitInterval = mkOption {
+      default = "30s";
+      type = types.str;
+      description = ''
+        Configures the rate limiting interval that is applied to all
+        messages generated on the system. This rate limiting is applied
+        per-service, so that two services which log do not interfere with
+        each other's limit. The value may be specified in the following
+        units: s, min, h, ms, us. To turn off any kind of rate limiting,
+        set either value to 0.
+
+        See <option>services.journald.rateLimitBurst</option> for important
+        considerations when setting this value.
+      '';
+    };
+
+    services.journald.rateLimitBurst = mkOption {
+      default = 10000;
+      type = types.int;
+      description = ''
+        Configures the rate limiting burst limit (number of messages per
+        interval) that is applied to all messages generated on the system.
+        This rate limiting is applied per-service, so that two services
+        which log do not interfere with each other's limit.
+
+        Note that the effective rate limit is multiplied by a factor derived
+        from the available free disk space for the journal as described on
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
+        journald.conf(5)</link>.
+
+        Note that the total amount of logs stored is limited by journald settings
+        such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
+
+        It is thus recommended to compute what period of time that you will be
+        able to store logs for when an application logs at full burst rate.
+        With default settings for log lines that are 100 Bytes long, this can
+        amount to just a few hours.
+      '';
+    };
+
+    services.journald.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "Storage=volatile";
+      description = ''
+        Extra config options for systemd-journald. See man journald.conf
+        for available options.
+      '';
+    };
+
+    services.journald.enableHttpGateway = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable the HTTP gateway to the journal.
+      '';
+    };
+
+    services.journald.forwardToSyslog = mkOption {
+      default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
+      defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
+      type = types.bool;
+      description = ''
+        Whether to forward log messages to syslog.
+      '';
+    };
+  };
+
+  config = {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-journald.socket"
+      "systemd-journald@.socket"
+      "systemd-journald-varlink@.socket"
+      "systemd-journald.service"
+      "systemd-journald@.service"
+      "systemd-journal-flush.service"
+      "systemd-journal-catalog-update.service"
+      ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
+      "systemd-journald-dev-log.socket"
+      "syslog.socket"
+      ] ++ optionals cfg.enableHttpGateway [
+      "systemd-journal-gatewayd.socket"
+      "systemd-journal-gatewayd.service"
+      ];
+
+    environment.etc = {
+      "systemd/journald.conf".text = ''
+        [Journal]
+        Storage=persistent
+        RateLimitInterval=${cfg.rateLimitInterval}
+        RateLimitBurst=${toString cfg.rateLimitBurst}
+        ${optionalString (cfg.console != "") ''
+          ForwardToConsole=yes
+          TTYPath=${cfg.console}
+        ''}
+        ${optionalString (cfg.forwardToSyslog) ''
+          ForwardToSyslog=yes
+        ''}
+        ${cfg.extraConfig}
+      '';
+    };
+
+    users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
+    users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
+    users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
+    users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
+
+    systemd.sockets.systemd-journal-gatewayd.wantedBy =
+      optional cfg.enableHttpGateway "sockets.target";
+
+    systemd.services.systemd-journal-flush.restartIfChanged = false;
+    systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
+    systemd.services.systemd-journald.stopIfChanged = false;
+    systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
+    systemd.services."systemd-journald@".stopIfChanged = false;
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/logind.nix b/nixos/modules/system/boot/systemd/logind.nix
new file mode 100644
index 00000000000..c1e6cfe61d0
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/logind.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logind;
+
+  logindHandlerType = types.enum [
+    "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
+    "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
+  ];
+in
+{
+  options = {
+    services.logind.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "IdleAction=lock";
+      description = ''
+        Extra config options for systemd-logind. See
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
+        logind.conf(5)</link> for available options.
+      '';
+    };
+
+    services.logind.killUserProcesses = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Specifies whether the processes of a user should be killed
+        when the user logs out.  If true, the scope unit corresponding
+        to the session and all processes inside that scope will be
+        terminated.  If false, the scope is "abandoned" (see
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
+        systemd.scope(5)</link>), and processes are not killed.
+        </para>
+
+        <para>
+        See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
+        for more details.
+      '';
+    };
+
+    services.logind.lidSwitch = mkOption {
+      default = "suspend";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to be done when the laptop lid is closed.
+      '';
+    };
+
+    services.logind.lidSwitchDocked = mkOption {
+      default = "ignore";
+      example = "suspend";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to be done when the laptop lid is closed
+        and another screen is added.
+      '';
+    };
+
+    services.logind.lidSwitchExternalPower = mkOption {
+      default = cfg.lidSwitch;
+      defaultText = literalExpression "services.logind.lidSwitch";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to do when the laptop lid is closed and the system is
+        on external power. By default use the same action as specified in
+        services.logind.lidSwitch.
+      '';
+    };
+  };
+
+  config = {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-logind.service"
+      "autovt@.service"
+      "systemd-user-sessions.service"
+      "dbus-org.freedesktop.import1.service"
+      "dbus-org.freedesktop.machine1.service"
+      "dbus-org.freedesktop.login1.service"
+      "user@.service"
+      "user-runtime-dir@.service"
+    ];
+
+    environment.etc = {
+      "systemd/logind.conf".text = ''
+        [Login]
+        KillUserProcesses=${if cfg.killUserProcesses then "yes" else "no"}
+        HandleLidSwitch=${cfg.lidSwitch}
+        HandleLidSwitchDocked=${cfg.lidSwitchDocked}
+        HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower}
+        ${cfg.extraConfig}
+      '';
+    };
+
+    # Restarting systemd-logind breaks X11
+    # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
+    # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
+    # - this might be addressed in the future by xorg
+    #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
+    systemd.services.systemd-logind.restartIfChanged = false;
+    systemd.services.systemd-logind.stopIfChanged = false;
+
+    # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
+    systemd.services."user-runtime-dir@".stopIfChanged = false;
+    systemd.services."user-runtime-dir@".restartIfChanged = false;
+  };
+}
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix
index 0c6822319a5..bf9995d03cc 100644
--- a/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixos/modules/system/boot/systemd/nspawn.nix
@@ -116,7 +116,13 @@ in {
     in
       mkMerge [
         (mkIf (cfg != {}) {
-          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []);
+          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits {
+            allowCollisions = false;
+            type = "nspawn";
+            inherit units;
+            upstreamUnits = [];
+            upstreamWants = [];
+          });
         })
         {
           systemd.targets.multi-user.wants = [ "machines.target" ];
diff --git a/nixos/modules/system/boot/systemd/shutdown.nix b/nixos/modules/system/boot/systemd/shutdown.nix
new file mode 100644
index 00000000000..93426931667
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/shutdown.nix
@@ -0,0 +1,32 @@
+{ config, lib, ... }: let
+
+  cfg = config.boot.systemd.shutdown;
+
+in {
+  options.boot.systemd.shutdown = {
+    enable = lib.mkEnableOption "pivoting back to an initramfs for shutdown" // { default = true; };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.generate-shutdown-ramfs = {
+      description = "Generate shutdown ramfs";
+      before = [ "shutdown.target" ];
+      unitConfig = {
+        DefaultDependencies = false;
+        ConditionFileIsExecutable = [
+          "!/run/initramfs/shutdown"
+          "/run/current-system/systemd/lib/systemd/systemd-shutdown"
+        ];
+      };
+
+      serviceConfig.Type = "oneshot";
+      script = ''
+        mkdir -p /run/initramfs
+        if ! mountpoint -q /run/initramfs; then
+          mount -t tmpfs tmpfs /run/initramfs
+        fi
+        cp /run/current-system/systemd/lib/systemd/systemd-shutdown /run/initramfs/shutdown
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
new file mode 100644
index 00000000000..97d60e9d652
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.systemd.tmpfiles;
+  systemd = config.systemd.package;
+in
+{
+  options = {
+    systemd.tmpfiles.rules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "d /tmp 1777 root root 10d" ];
+      description = ''
+        Rules for creation, deletion and cleaning of volatile and temporary files
+        automatically. See
+        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the exact format.
+      '';
+    };
+
+    systemd.tmpfiles.packages = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression "[ pkgs.lvm2 ]";
+      apply = map getLib;
+      description = ''
+        List of packages containing <command>systemd-tmpfiles</command> rules.
+
+        All files ending in .conf found in
+        <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
+        will be included.
+        If this folder does not exist or does not contain any files an error will be returned instead.
+
+        If a <filename>lib</filename> output is available, rules are searched there and only there.
+        If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
+        and if that does not exist either, the default output will be used.
+      '';
+    };
+  };
+
+  config = {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
+      "systemd-tmpfiles-setup-dev.service"
+    ];
+
+    systemd.additionalUpstreamUserUnits = [
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
+    ];
+
+    environment.etc = {
+      "tmpfiles.d".source = (pkgs.symlinkJoin {
+        name = "tmpfiles.d";
+        paths = map (p: p + "/lib/tmpfiles.d") cfg.packages;
+        postBuild = ''
+          for i in $(cat $pathsPath); do
+            (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
+              echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
+              exit 1
+            )
+          done
+        '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
+          rm -f $out/${removePrefix "tmpfiles.d/" name}
+        '') config.system.build.etc.passthru.targets;
+      }) + "/*";
+    };
+
+    systemd.tmpfiles.packages = [
+      # Default tmpfiles rules provided by systemd
+      (pkgs.runCommand "systemd-default-tmpfiles" {} ''
+        mkdir -p $out/lib/tmpfiles.d
+        cd $out/lib/tmpfiles.d
+
+        ln -s "${systemd}/example/tmpfiles.d/home.conf"
+        ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
+        ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
+        ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
+        ln -s "${systemd}/example/tmpfiles.d/var.conf"
+        ln -s "${systemd}/example/tmpfiles.d/x11.conf"
+      '')
+      # User-specified tmpfiles rules
+      (pkgs.writeTextFile {
+        name = "nixos-tmpfiles.d";
+        destination = "/lib/tmpfiles.d/00-nixos.conf";
+        text = ''
+          # This file is created automatically and should not be modified.
+          # Please change the option ‘systemd.tmpfiles.rules’ instead.
+
+          ${concatStringsSep "\n" cfg.rules}
+        '';
+      })
+    ];
+
+    systemd.tmpfiles.rules = [
+      "d  /nix/var                           0755 root root - -"
+      "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
+      "d  /run/lock                          0755 root root - -"
+      "d  /var/db                            0755 root root - -"
+      "L  /etc/mtab                          -    -    -    - ../proc/mounts"
+      "L  /var/lock                          -    -    -    - ../run/lock"
+      # Boot-time cleanup
+      "R! /etc/group.lock                    -    -    -    - -"
+      "R! /etc/passwd.lock                   -    -    -    - -"
+      "R! /etc/shadow.lock                   -    -    -    - -"
+      "R! /etc/mtab*                         -    -    -    - -"
+      "R! /nix/var/nix/gcroots/tmp           -    -    -    - -"
+      "R! /nix/var/nix/temproots             -    -    -    - -"
+    ];
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix
new file mode 100644
index 00000000000..4951aef9558
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/user.nix
@@ -0,0 +1,153 @@
+{ config, lib, pkgs, utils, ... }:
+with utils;
+with systemdUtils.unitOptions;
+with lib;
+
+let
+  cfg = config.systemd.user;
+
+  systemd = config.systemd.package;
+
+  inherit
+    (systemdUtils.lib)
+    makeUnit
+    generateUnits
+    targetToUnit
+    serviceToUnit
+    socketToUnit
+    timerToUnit
+    pathToUnit;
+
+  upstreamUserUnits = [
+    "app.slice"
+    "background.slice"
+    "basic.target"
+    "bluetooth.target"
+    "default.target"
+    "exit.target"
+    "graphical-session-pre.target"
+    "graphical-session.target"
+    "paths.target"
+    "printer.target"
+    "session.slice"
+    "shutdown.target"
+    "smartcard.target"
+    "sockets.target"
+    "sound.target"
+    "systemd-exit.service"
+    "timers.target"
+    "xdg-desktop-autostart.target"
+  ] ++ config.systemd.additionalUpstreamUserUnits;
+in {
+  options = {
+    systemd.user.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "DefaultCPUAccounting=yes";
+      description = ''
+        Extra config options for systemd user instances. See man systemd-user.conf for
+        available options.
+      '';
+    };
+
+    systemd.user.units = mkOption {
+      description = "Definition of systemd per-user units.";
+      default = {};
+      type = systemdUtils.types.units;
+    };
+
+    systemd.user.paths = mkOption {
+      default = {};
+      type = systemdUtils.types.paths;
+      description = "Definition of systemd per-user path units.";
+    };
+
+    systemd.user.services = mkOption {
+      default = {};
+      type = systemdUtils.types.services;
+      description = "Definition of systemd per-user service units.";
+    };
+
+    systemd.user.slices = mkOption {
+      default = {};
+      type = systemdUtils.types.slices;
+      description = "Definition of systemd per-user slice units.";
+    };
+
+    systemd.user.sockets = mkOption {
+      default = {};
+      type = systemdUtils.types.sockets;
+      description = "Definition of systemd per-user socket units.";
+    };
+
+    systemd.user.targets = mkOption {
+      default = {};
+      type = systemdUtils.types.targets;
+      description = "Definition of systemd per-user target units.";
+    };
+
+    systemd.user.timers = mkOption {
+      default = {};
+      type = systemdUtils.types.timers;
+      description = "Definition of systemd per-user timer units.";
+    };
+
+    systemd.additionalUpstreamUserUnits = mkOption {
+      default = [];
+      type = types.listOf types.str;
+      example = [];
+      description = ''
+        Additional units shipped with systemd that should be enabled for per-user systemd instances.
+      '';
+      internal = true;
+    };
+  };
+
+  config = {
+    systemd.additionalUpstreamSystemUnits = [
+      "user.slice"
+    ];
+
+    environment.etc = {
+      "systemd/user".source = generateUnits {
+        type = "user";
+        inherit (cfg) units;
+        upstreamUnits = upstreamUserUnits;
+        upstreamWants = [];
+      };
+
+      "systemd/user.conf".text = ''
+        [Manager]
+        ${cfg.extraConfig}
+      '';
+    };
+
+    systemd.user.units =
+         mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
+      // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
+      // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.slices
+      // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.sockets
+      // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.targets
+      // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.timers;
+
+    # Generate timer units for all services that have a ‘startAt’ value.
+    systemd.user.timers =
+      mapAttrs (name: service: {
+        wantedBy = ["timers.target"];
+        timerConfig.OnCalendar = service.startAt;
+      })
+      (filterAttrs (name: service: service.startAt != []) cfg.services);
+
+    # Provide the systemd-user PAM service, required to run systemd
+    # user instances.
+    security.pam.services.systemd-user =
+      { # Ensure that pam_systemd gets included. This is special-cased
+        # in systemd to provide XDG_RUNTIME_DIR.
+        startSession = true;
+      };
+
+    # Some overrides to upstream units.
+    systemd.services."user@".restartIfChanged = false;
+    systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
+  };
+}
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index 5f35a154769..6279957fcd6 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -60,15 +60,27 @@ with lib;
     };
     users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
 
-    system.activationScripts.systemd-timesyncd-migration = mkIf (versionOlder config.system.stateVersion "19.09") ''
+    system.activationScripts.systemd-timesyncd-migration =
       # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
       #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
       #  - https://github.com/systemd/systemd/issues/12131
-      if [ -L /var/lib/systemd/timesync ]; then
-        rm /var/lib/systemd/timesync
-        mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+      mkIf (versionOlder config.system.stateVersion "19.09") ''
+        if [ -L /var/lib/systemd/timesync ]; then
+          rm /var/lib/systemd/timesync
+          mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+        fi
+      '';
+    system.activationScripts.systemd-timesyncd-init-clock =
+      # Ensure that we have some stored time to prevent systemd-timesyncd to
+      # resort back to the fallback time.
+      # If the file doesn't exist we assume that our current system clock is
+      # good enough to provide an initial value.
+      ''
+      if ! [ -f /var/lib/systemd/timesync/clock ]; then
+        test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
+        touch /var/lib/systemd/timesync/clock
       fi
-    '';
+      '';
   };
 
 }
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 1404dcbaf7c..d00dc761d6e 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -63,13 +63,16 @@ in {
       };
 
       dates = mkOption {
-        default = "04:40";
         type = types.str;
+        default = "04:40";
+        example = "daily";
         description = ''
-          Specification (in the format described by
+          How often or when upgrade occurs. For most desktop and server systems
+          a sufficient upgrade frequency is once a day.
+
+          The format is described in
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>) of the time at
-          which the update will occur.
+          <manvolnum>7</manvolnum></citerefentry>.
         '';
       };
 
@@ -90,7 +93,7 @@ in {
         example = "45min";
         description = ''
           Add a randomized delay before each automatic upgrade.
-          The delay will be chozen between zero and this value.
+          The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
           <manvolnum>7</manvolnum></citerefentry>
@@ -123,6 +126,22 @@ in {
         });
       };
 
+      persistent = mkOption {
+        default = true;
+        type = types.bool;
+        example = false;
+        description = ''
+          Takes a boolean argument. If true, the time when the service
+          unit was last triggered is stored on disk. When the timer is
+          activated, the service unit is triggered immediately if it
+          would have been triggered at least once during the time when
+          the timer was inactive. Such triggering is nonetheless
+          subject to the delay imposed by RandomizedDelaySec=. This is
+          useful to catch up on missed runs of the service when the
+          system was powered down.
+        '';
+      };
+
     };
 
   };
@@ -217,11 +236,17 @@ in {
       '';
 
       startAt = cfg.dates;
-    };
 
-    systemd.timers.nixos-upgrade.timerConfig.RandomizedDelaySec =
-      cfg.randomizedDelaySec;
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+    };
 
+    systemd.timers.nixos-upgrade = {
+      timerConfig = {
+        RandomizedDelaySec = cfg.randomizedDelaySec;
+        Persistent = cfg.persistent;
+      };
+    };
   };
 
 }
diff --git a/nixos/modules/tasks/bcache.nix b/nixos/modules/tasks/bcache.nix
index 41fb7664f3d..0a13522de11 100644
--- a/nixos/modules/tasks/bcache.nix
+++ b/nixos/modules/tasks/bcache.nix
@@ -1,13 +1,23 @@
-{ pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 {
+  options.boot.initrd.services.bcache.enable = (lib.mkEnableOption "bcache support in the initrd") // {
+    visible = false; # only works with systemd stage 1
+  };
 
-  environment.systemPackages = [ pkgs.bcache-tools ];
+  config = {
 
-  services.udev.packages = [ pkgs.bcache-tools ];
+    environment.systemPackages = [ pkgs.bcache-tools ];
 
-  boot.initrd.extraUdevRulesCommands = ''
-    cp -v ${pkgs.bcache-tools}/lib/udev/rules.d/*.rules $out/
-  '';
+    services.udev.packages = [ pkgs.bcache-tools ];
 
+    boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
+      cp -v ${pkgs.bcache-tools}/lib/udev/rules.d/*.rules $out/
+    '';
+
+    boot.initrd.services.udev = lib.mkIf config.boot.initrd.services.bcache.enable {
+      packages = [ pkgs.bcache-tools ];
+      binPackages = [ pkgs.bcache-tools ];
+    };
+  };
 }
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index f3da6771197..b8afe231dd2 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -215,6 +215,35 @@ in
       '';
     };
 
+    boot.devSize = mkOption {
+      default = "5%";
+      example = "32m";
+      type = types.str;
+      description = ''
+        Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
+
+    boot.devShmSize = mkOption {
+      default = "50%";
+      example = "256m";
+      type = types.str;
+      description = ''
+        Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
+
+    boot.runSize = mkOption {
+      default = "25%";
+      example = "256m";
+      type = types.str;
+      description = ''
+        Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
   };
 
 
@@ -323,7 +352,7 @@ in
             unitConfig.DefaultDependencies = false; # needed to prevent a cycle
             serviceConfig.Type = "oneshot";
           };
-      in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)) // {
+      in listToAttrs (map formatDevice (filter (fs: fs.autoFormat && !(utils.fsNeededForBoot fs)) fileSystems)) // {
     # Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
     # This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
         "mount-pstore" = {
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index ae1dab5b8d8..b7ebc37dd5c 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -66,19 +66,19 @@ in
         ]
       );
 
-      boot.initrd.extraUtilsCommands = mkIf inInitrd
+      boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
         ln -sv btrfs $out/bin/btrfsck
         ln -sv btrfsck $out/bin/fsck.btrfs
       '';
 
-      boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
+      boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         $out/bin/btrfs --version
       '';
 
-      boot.initrd.postDeviceCommands = mkIf inInitrd
+      boot.initrd.postDeviceCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         btrfs device scan
       '';
diff --git a/nixos/modules/tasks/filesystems/cifs.nix b/nixos/modules/tasks/filesystems/cifs.nix
index 47ba0c03c56..0de292a6920 100644
--- a/nixos/modules/tasks/filesystems/cifs.nix
+++ b/nixos/modules/tasks/filesystems/cifs.nix
@@ -16,7 +16,7 @@ in
     boot.initrd.availableKernelModules = mkIf inInitrd
       [ "cifs" "nls_utf8" "hmac" "md4" "ecb" "des_generic" "sha256" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.cifs-utils}/sbin/mount.cifs
       '';
diff --git a/nixos/modules/tasks/filesystems/ext.nix b/nixos/modules/tasks/filesystems/ext.nix
index a14a3ac3854..9b61f21643a 100644
--- a/nixos/modules/tasks/filesystems/ext.nix
+++ b/nixos/modules/tasks/filesystems/ext.nix
@@ -1,14 +1,20 @@
-{ pkgs, ... }:
+{ config, lib, pkgs, ... }:
+
+let
+
+  inInitrd = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.initrd.supportedFilesystems;
+
+in
 
 {
   config = {
 
-    system.fsPackages = [ pkgs.e2fsprogs ];
+    system.fsPackages = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [ pkgs.e2fsprogs ];
 
     # As of kernel 4.3, there is no separate ext3 driver (they're also handled by ext4.ko)
-    boot.initrd.availableKernelModules = [ "ext2" "ext4" ];
+    boot.initrd.availableKernelModules = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [ "ext2" "ext4" ];
 
-    boot.initrd.extraUtilsCommands =
+    boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
       ''
         # Copy e2fsck and friends.
         copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/e2fsck
diff --git a/nixos/modules/tasks/filesystems/f2fs.nix b/nixos/modules/tasks/filesystems/f2fs.nix
index a305235979a..1d52861aa39 100644
--- a/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixos/modules/tasks/filesystems/f2fs.nix
@@ -13,7 +13,7 @@ in
 
     boot.initrd.availableKernelModules = mkIf inInitrd [ "f2fs" "crc32" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd ''
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
       ${optionalString (any (fs: fs.autoResize) fileSystems) ''
         # We need f2fs-tools' tools to resize filesystems
diff --git a/nixos/modules/tasks/filesystems/jfs.nix b/nixos/modules/tasks/filesystems/jfs.nix
index fc3905c7dc2..700f05af2be 100644
--- a/nixos/modules/tasks/filesystems/jfs.nix
+++ b/nixos/modules/tasks/filesystems/jfs.nix
@@ -12,7 +12,7 @@ in
 
     boot.initrd.kernelModules = mkIf inInitrd [ "jfs" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd ''
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
     '';
   };
diff --git a/nixos/modules/tasks/filesystems/reiserfs.nix b/nixos/modules/tasks/filesystems/reiserfs.nix
index ab4c43e2ab8..7b017a83db8 100644
--- a/nixos/modules/tasks/filesystems/reiserfs.nix
+++ b/nixos/modules/tasks/filesystems/reiserfs.nix
@@ -15,7 +15,7 @@ in
 
     boot.initrd.kernelModules = mkIf inInitrd [ "reiserfs" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.reiserfsprogs}/sbin/reiserfsck
         ln -s reiserfsck $out/bin/fsck.reiserfs
diff --git a/nixos/modules/tasks/filesystems/unionfs-fuse.nix b/nixos/modules/tasks/filesystems/unionfs-fuse.nix
index f54f3559c34..f9954b5182f 100644
--- a/nixos/modules/tasks/filesystems/unionfs-fuse.nix
+++ b/nixos/modules/tasks/filesystems/unionfs-fuse.nix
@@ -6,7 +6,7 @@
     (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.initrd.supportedFilesystems) {
       boot.initrd.kernelModules = [ "fuse" ];
 
-      boot.initrd.extraUtilsCommands = ''
+      boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         copy_bin_and_libs ${pkgs.fuse}/sbin/mount.fuse
         copy_bin_and_libs ${pkgs.unionfs-fuse}/bin/unionfs
         substitute ${pkgs.unionfs-fuse}/sbin/mount.unionfs-fuse $out/bin/mount.unionfs-fuse \
@@ -16,12 +16,23 @@
         chmod +x $out/bin/mount.unionfs-fuse
       '';
 
-      boot.initrd.postDeviceCommands = ''
+      boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
           # Hacky!!! fuse hard-codes the path to mount
           mkdir -p /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
           ln -s $(which mount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
           ln -s $(which umount) /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-${pkgs.util-linux.name}-bin/bin
         '';
+
+      boot.initrd.systemd.extraBin = {
+        "mount.fuse" = "${pkgs.fuse}/bin/mount.fuse";
+        "unionfs" = "${pkgs.unionfs-fuse}/bin/unionfs";
+        "mount.unionfs-fuse" = pkgs.runCommand "mount.unionfs-fuse" {} ''
+          substitute ${pkgs.unionfs-fuse}/sbin/mount.unionfs-fuse $out \
+            --replace '${pkgs.bash}/bin/bash' /bin/sh \
+            --replace '${pkgs.fuse}/sbin' /bin \
+            --replace '${pkgs.unionfs-fuse}/bin' /bin
+        '';
+      };
     })
 
     (lib.mkIf (lib.any (fs: fs == "unionfs-fuse") config.boot.supportedFilesystems) {
diff --git a/nixos/modules/tasks/filesystems/vfat.nix b/nixos/modules/tasks/filesystems/vfat.nix
index 958e27ae8a3..5baab1c802c 100644
--- a/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixos/modules/tasks/filesystems/vfat.nix
@@ -15,7 +15,7 @@ in
 
     boot.initrd.kernelModules = mkIf inInitrd [ "vfat" "nls_cp437" "nls_iso8859-1" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.dosfstools}/sbin/dosfsck
         ln -sv dosfsck $out/bin/fsck.vfat
diff --git a/nixos/modules/tasks/filesystems/xfs.nix b/nixos/modules/tasks/filesystems/xfs.nix
index 98038701ca5..f81f5864655 100644
--- a/nixos/modules/tasks/filesystems/xfs.nix
+++ b/nixos/modules/tasks/filesystems/xfs.nix
@@ -15,14 +15,14 @@ in
 
     boot.initrd.availableKernelModules = mkIf inInitrd [ "xfs" "crc32c" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/fsck.xfs
         copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/xfs_repair
       '';
 
     # Trick just to set 'sh' after the extraUtils nuke-refs.
-    boot.initrd.extraUtilsCommandsTest = mkIf inInitrd
+    boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
       ''
         sed -i -e 's,^#!.*,#!'$out/bin/sh, $out/bin/fsck.xfs
       '';
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 3bc0dedec00..fbfc61177d3 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -642,41 +642,14 @@ in
         };
 
         scriptArgs = "%i";
-        path = [ pkgs.gawk cfgZfs.package ];
-
-        # ZFS has no way of enumerating just devices in a pool in a way
-        # that 'zpool online -e' supports. Thus, we've implemented a
-        # bit of a strange approach of highlighting just devices.
-        # See: https://github.com/openzfs/zfs/issues/12505
-        script = let
-          # This UUID has been chosen at random and is to provide a
-          # collision-proof, predictable token to search for
-          magicIdentifier = "NIXOS-ZFS-ZPOOL-DEVICE-IDENTIFIER-37108bec-aff6-4b58-9e5e-53c7c9766f05";
-          zpoolScripts = pkgs.writeShellScriptBin "device-highlighter" ''
-            echo "${magicIdentifier}"
-          '';
-        in ''
+        path = [ cfgZfs.package ];
+
+        script =  ''
           pool=$1
 
           echo "Expanding all devices for $pool."
 
-          # Put our device-highlighter script it to the PATH
-          export ZPOOL_SCRIPTS_PATH=${zpoolScripts}/bin
-
-          # Enable running our precisely specified zpool script as root
-          export ZPOOL_SCRIPTS_AS_ROOT=1
-
-          devices() (
-            zpool status -c device-highlighter "$pool" \
-             | awk '($2 == "ONLINE" && $6 == "${magicIdentifier}") { print $1; }'
-          )
-
-          for device in $(devices); do
-            echo "Attempting to expand $device of $pool..."
-            if ! zpool online -e "$pool" "$device"; then
-              echo "Failed to expand '$device' of '$pool'."
-            fi
-          done
+          ${pkgs.zpool-auto-expand-partitions}/bin/zpool_part_disks --automatically-grow "$pool"
         '';
       };
 
@@ -701,8 +674,6 @@ in
             RemainAfterExit = true;
           };
 
-          path = [ pkgs.gawk cfgZfs.package ];
-
           script = ''
             for pool in ${poolListProvider}; do
               systemctl start --no-block "zpool-expand@$pool"
diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix
index 35316603c38..4108b482cce 100644
--- a/nixos/modules/tasks/lvm.nix
+++ b/nixos/modules/tasks/lvm.nix
@@ -7,17 +7,22 @@ in {
   options.services.lvm = {
     package = mkOption {
       type = types.package;
-      default = if cfg.dmeventd.enable then pkgs.lvm2_dmeventd else pkgs.lvm2;
+      default = pkgs.lvm2;
       internal = true;
       defaultText = literalExpression "pkgs.lvm2";
       description = ''
         This option allows you to override the LVM package that's used on the system
         (udev rules, tmpfiles, systemd services).
-        Defaults to pkgs.lvm2, or pkgs.lvm2_dmeventd if dmeventd is enabled.
+        Defaults to pkgs.lvm2, pkgs.lvm2_dmeventd if dmeventd or pkgs.lvm2_vdo if vdo is enabled.
       '';
     };
     dmeventd.enable = mkEnableOption "the LVM dmevent daemon";
     boot.thin.enable = mkEnableOption "support for booting from ThinLVs";
+    boot.vdo.enable = mkEnableOption "support for booting from VDOLVs";
+  };
+
+  options.boot.initrd.services.lvm.enable = (mkEnableOption "enable booting from LVM2 in the initrd") // {
+    visible = false;
   };
 
   config = mkMerge [
@@ -30,8 +35,13 @@ in {
       environment.systemPackages = [ cfg.package ];
       systemd.packages = [ cfg.package ];
 
-      # TODO: update once https://github.com/NixOS/nixpkgs/pull/93006 was merged
       services.udev.packages = [ cfg.package.out ];
+
+      # We need lvm2 for the device-mapper rules
+      boot.initrd.services.udev.packages = lib.mkIf config.boot.initrd.services.lvm.enable [ cfg.package ];
+      # The device-mapper rules want to call tools from lvm2
+      boot.initrd.systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ cfg.package ];
+      boot.initrd.services.udev.binPackages = lib.mkIf config.boot.initrd.services.lvm.enable [ cfg.package ];
     })
     (mkIf cfg.dmeventd.enable {
       systemd.sockets."dm-event".wantedBy = [ "sockets.target" ];
@@ -40,18 +50,21 @@ in {
       environment.etc."lvm/lvm.conf".text = ''
         dmeventd/executable = "${cfg.package}/bin/dmeventd"
       '';
+      services.lvm.package = mkDefault pkgs.lvm2_dmeventd;
     })
     (mkIf cfg.boot.thin.enable {
       boot.initrd = {
         kernelModules = [ "dm-snapshot" "dm-thin-pool" ];
 
-        extraUtilsCommands = ''
+        systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ pkgs.thin-provisioning-tools ];
+
+        extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
           for BIN in ${pkgs.thin-provisioning-tools}/bin/*; do
             copy_bin_and_libs $BIN
           done
         '';
 
-        extraUtilsCommandsTest = ''
+        extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
           ls ${pkgs.thin-provisioning-tools}/bin/ | grep -v pdata_tools | while read BIN; do
             $out/bin/$(basename $BIN) --help > /dev/null
           done
@@ -61,9 +74,45 @@ in {
       environment.etc."lvm/lvm.conf".text = concatMapStringsSep "\n"
         (bin: "global/${bin}_executable = ${pkgs.thin-provisioning-tools}/bin/${bin}")
         [ "thin_check" "thin_dump" "thin_repair" "cache_check" "cache_dump" "cache_repair" ];
+
+      environment.systemPackages = [ pkgs.thin-provisioning-tools ];
+    })
+    (mkIf cfg.boot.vdo.enable {
+      boot = {
+        initrd = {
+          kernelModules = [ "kvdo" ];
+
+          systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ pkgs.vdo ];
+
+          extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)''
+            ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
+              copy_bin_and_libs ${pkgs.vdo}/bin/$BIN
+            done
+          '';
+
+          extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)''
+            ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
+              $out/bin/$(basename $BIN) --help > /dev/null
+            done
+          '';
+        };
+        extraModulePackages = [ config.boot.kernelPackages.kvdo ];
+      };
+
+      services.lvm.package = mkOverride 999 pkgs.lvm2_vdo;  # this overrides mkDefault
+
+      environment.systemPackages = [ pkgs.vdo ];
     })
     (mkIf (cfg.dmeventd.enable || cfg.boot.thin.enable) {
-      boot.initrd.preLVMCommands = ''
+      boot.initrd.systemd.contents."/etc/lvm/lvm.conf".text = optionalString (config.boot.initrd.services.lvm.enable && cfg.boot.thin.enable) (concatMapStringsSep "\n"
+          (bin: "global/${bin}_executable = /bin/${bin}")
+          [ "thin_check" "thin_dump" "thin_repair" "cache_check" "cache_dump" "cache_repair" ]
+        ) + "\n" + optionalString cfg.dmeventd.enable ''
+          dmeventd/executable = /bin/false
+          activation/monitoring = 0
+        '';
+
+      boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) ''
           mkdir -p /etc/lvm
           cat << EOF >> /etc/lvm/lvm.conf
           ${optionalString cfg.boot.thin.enable (
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 19f2be2c4a2..b0f160c1dbf 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -535,6 +535,7 @@ let
         createGreDevice = n: v: nameValuePair "${n}-netdev"
           (let
             deps = deviceDependency v.dev;
+            ttlarg = if lib.hasPrefix "ip6" v.type then "hoplimit" else "ttl";
           in
           { description = "GRE Tunnel Interface ${n}";
             wantedBy = [ "network-setup.service" (subsystemDevice n) ];
@@ -551,6 +552,7 @@ let
               ip link add name "${n}" type ${v.type} \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
+                ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
                 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
               ip link set "${n}" up
             '';
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 8a5e1b5af11..8654539b662 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -318,6 +318,8 @@ in
               Remote = gre.remote;
             }) // (optionalAttrs (gre.local != null) {
               Local = gre.local;
+            }) // (optionalAttrs (gre.ttl != null) {
+              TTL = gre.ttl;
             });
         };
         networks = mkIf (gre.dev != null) {
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 01980b80f1c..d09e9b99248 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -403,6 +403,15 @@ let
     </itemizedlist>
   '';
 
+  hostidFile = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
+      hi="${cfg.hostId}"
+      ${if pkgs.stdenv.isBigEndian then ''
+        echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
+      '' else ''
+        echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
+      ''}
+    '';
+
 in
 
 {
@@ -1020,12 +1029,14 @@ in
             local = "10.0.0.22";
             dev = "enp4s0f0";
             type = "tap";
+            ttl = 255;
           };
           gre6Tunnel = {
             remote = "fd7a:5634::1";
             local = "fd7a:5634::2";
             dev = "enp4s0f0";
             type = "tun6";
+            ttl = 255;
           };
         }
       '';
@@ -1063,6 +1074,15 @@ in
             '';
           };
 
+          ttl = mkOption {
+            type = types.nullOr types.int;
+            default = null;
+            example = 255;
+            description = ''
+              The time-to-live/hoplimit of the connection to the remote tunnel endpoint.
+            '';
+          };
+
           type = mkOption {
             type = with types; enum [ "tun" "tap" "tun6" "tap6" ];
             default = "tap";
@@ -1372,16 +1392,8 @@ in
         domainname "${cfg.domain}"
       '';
 
-    environment.etc.hostid = mkIf (cfg.hostId != null)
-      { source = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
-          hi="${cfg.hostId}"
-          ${if pkgs.stdenv.isBigEndian then ''
-            echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
-          '' else ''
-            echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
-          ''}
-        '';
-      };
+    environment.etc.hostid = mkIf (cfg.hostId != null) { source = hostidFile; };
+    boot.initrd.systemd.contents."/etc/hostid" = mkIf (cfg.hostId != null) { source = hostidFile; };
 
     # static hostname configuration needed for hostnamectl and the
     # org.freedesktop.hostname1 dbus service (both provided by systemd)
@@ -1440,7 +1452,7 @@ in
           sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
         in ''
           # enable and prefer IPv6 privacy addresses by default
-          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
+          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/$name/use_tempaddr'"
         '';
       })
       (pkgs.writeTextFile rec {
diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index 8fa19194bed..0b53a6d152d 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -1,17 +1,43 @@
-{ pkgs, ... }:
+{ config, pkgs, lib, ... }: let
 
-{
+  cfg = config.boot.initrd.services.swraid;
 
-  environment.systemPackages = [ pkgs.mdadm ];
+in {
 
-  services.udev.packages = [ pkgs.mdadm ];
+  options.boot.initrd.services.swraid = {
+    enable = (lib.mkEnableOption "swraid support using mdadm") // {
+      visible = false; # only has effect when the new stage 1 is in place
+    };
 
-  systemd.packages = [ pkgs.mdadm ];
+    mdadmConf = lib.mkOption {
+      description = "Contents of <filename>/etc/mdadm.conf</filename> in initrd.";
+      type = lib.types.lines;
+      default = "";
+    };
+  };
 
-  boot.initrd.availableKernelModules = [ "md_mod" "raid0" "raid1" "raid10" "raid456" ];
+  config = {
+    environment.systemPackages = [ pkgs.mdadm ];
 
-  boot.initrd.extraUdevRulesCommands = ''
-    cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
-  '';
+    services.udev.packages = [ pkgs.mdadm ];
 
+    systemd.packages = [ pkgs.mdadm ];
+
+    boot.initrd.availableKernelModules = lib.mkIf (config.boot.initrd.systemd.enable -> cfg.enable) [ "md_mod" "raid0" "raid1" "raid10" "raid456" ];
+
+    boot.initrd.extraUdevRulesCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
+      cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
+    '';
+
+    boot.initrd.systemd = lib.mkIf cfg.enable {
+      contents."/etc/mdadm.conf" = lib.mkIf (cfg.mdadmConf != "") {
+        text = cfg.mdadmConf;
+      };
+
+      packages = [ pkgs.mdadm ];
+      initrdBin = [ pkgs.mdadm ];
+    };
+
+    boot.initrd.services.udev.packages = lib.mkIf cfg.enable [ pkgs.mdadm ];
+  };
 }
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index bd8c7f8c1ee..e2425b44eac 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -146,15 +146,11 @@ in
 
     services.logrotate = {
       enable = true;
-      extraConfig = ''
-        /var/log/waagent.log {
-            compress
-            monthly
-            rotate 6
-            notifempty
-            missingok
-        }
-      '';
+      settings."/var/log/waagent.log" = {
+        compress = true;
+        frequency = "monthly";
+        rotate = 6;
+      };
     };
 
     systemd.targets.provisioned = {
diff --git a/nixos/modules/virtualisation/azure-common.nix b/nixos/modules/virtualisation/azure-common.nix
index 8efa177e30d..dc7853b9503 100644
--- a/nixos/modules/virtualisation/azure-common.nix
+++ b/nixos/modules/virtualisation/azure-common.nix
@@ -21,7 +21,11 @@ with lib;
   # way to select them anyway.
   boot.loader.grub.configurationLimit = 0;
 
-  fileSystems."/".device = "/dev/disk/by-label/nixos";
+  fileSystems."/" = {
+    device = "/dev/disk/by-label/nixos";
+    fsType = "ext4";
+    autoResize = true;
+  };
 
   # Allow root logins only using the SSH key that the user specified
   # at instance creation time, ping client connections to avoid timeouts
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index ab87394a30e..e0bccb83a97 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -344,6 +344,10 @@ in
       restartIfChanged = false;
     };
 
+    systemd.services.virtchd = {
+      path = [ pkgs.cloud-hypervisor ];
+    };
+
     systemd.services.libvirt-guests = {
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ coreutils gawk cfg.package ];
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 0838a57f0f3..23228a109bc 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -4,6 +4,11 @@ with lib;
 
 let
 
+  configurationPrefix = optionalString (versionAtLeast config.system.stateVersion "22.05") "nixos-";
+  configurationDirectoryName = "${configurationPrefix}containers";
+  configurationDirectory = "/etc/${configurationDirectoryName}";
+  stateDirectory = "/var/lib/${configurationPrefix}containers";
+
   # The container's init script, a small wrapper around the regular
   # NixOS stage-2 init script.
   containerInit = (cfg:
@@ -77,7 +82,7 @@ let
   startScript = cfg:
     ''
       mkdir -p -m 0755 "$root/etc" "$root/var/lib"
-      mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers
+      mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/nixos-containers
       if ! [ -e "$root/etc/os-release" ]; then
         touch "$root/etc/os-release"
       fi
@@ -249,11 +254,11 @@ let
 
     SyslogIdentifier = "container %i";
 
-    EnvironmentFile = "-/etc/containers/%i.conf";
+    EnvironmentFile = "-${configurationDirectory}/%i.conf";
 
     Type = "notify";
 
-    RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i";
+    RuntimeDirectory = lib.optional cfg.ephemeral "${configurationDirectoryName}/%i";
 
     # Note that on reboot, systemd-nspawn returns 133, so this
     # unit will be restarted. On poweroff, it returns 0, so the
@@ -737,15 +742,21 @@ in
 
   config = mkIf (config.boot.enableContainers) (let
 
+    warnings = flatten [
+      (optional (config.virtualisation.containers.enable && versionOlder config.system.stateVersion "22.05") ''
+        Enabling both boot.enableContainers & virtualisation.containers on system.stateVersion < 22.05 is unsupported.
+      '')
+    ];
+
     unit = {
       description = "Container '%i'";
 
-      unitConfig.RequiresMountsFor = "/var/lib/containers/%i";
+      unitConfig.RequiresMountsFor = "${stateDirectory}/%i";
 
       path = [ pkgs.iproute2 ];
 
       environment = {
-        root = "/var/lib/containers/%i";
+        root = "${stateDirectory}/%i";
         INSTANCE = "%i";
       };
 
@@ -782,8 +793,8 @@ in
             script = startScript containerConfig;
             postStart = postStartScript containerConfig;
             serviceConfig = serviceDirectives containerConfig;
-            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i";
-            environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i";
+            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
+            environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
           } // (
           if containerConfig.autoStart then
             {
@@ -792,7 +803,7 @@ in
               after = [ "network.target" ];
               restartTriggers = [
                 containerConfig.path
-                config.environment.etc."containers/${name}.conf".source
+                config.environment.etc."${configurationDirectoryName}/${name}.conf".source
               ];
               restartIfChanged = true;
             }
@@ -800,12 +811,12 @@ in
       )) config.containers)
     ));
 
-    # Generate a configuration file in /etc/containers for each
+    # Generate a configuration file in /etc/nixos-containers for each
     # container so that container@.target can get the container
     # configuration.
     environment.etc =
       let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort);
-      in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf"
+      in mapAttrs' (name: cfg: nameValuePair "${configurationDirectoryName}/${name}.conf"
       { text =
           ''
             SYSTEM_PATH=${cfg.path}
@@ -854,7 +865,11 @@ in
       ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
     '';
 
-    environment.systemPackages = [ pkgs.nixos-container ];
+    environment.systemPackages = [
+      (pkgs.nixos-container.override {
+        inherit stateDirectory configurationDirectory;
+      })
+    ];
 
     boot.kernelModules = [
       "bridge"
diff --git a/nixos/modules/virtualisation/openstack-config.nix b/nixos/modules/virtualisation/openstack-config.nix
index d01e0f23aba..af4f5746610 100644
--- a/nixos/modules/virtualisation/openstack-config.nix
+++ b/nixos/modules/virtualisation/openstack-config.nix
@@ -1,8 +1,11 @@
-{ pkgs, lib, ... }:
+{ config, pkgs, lib, ... }:
 
-with lib;
+# image metadata:
+# hw_firmware_type=uefi
 
 let
+  inherit (lib) mkIf mkDefault;
+  cfg = config.openstack;
   metadataFetcher = import ./openstack-metadata-fetcher.nix {
     targetRoot = "/";
     wgetExtraOptions = "--retry-connrefused";
@@ -11,23 +14,47 @@ in
 {
   imports = [
     ../profiles/qemu-guest.nix
+
+    # Note: While we do use the headless profile, we also explicitly
+    # turn on the serial console on tty1 below.
+    # Note that I could not find any documentation indicating tty1 was
+    # the correct choice. I picked tty1 because that is what one
+    # particular host was using.
     ../profiles/headless.nix
+
     # The Openstack Metadata service exposes data on an EC2 API also.
     ./ec2-data.nix
     ./amazon-init.nix
   ];
 
   config = {
-    fileSystems."/" = {
+    fileSystems."/" = mkIf (!cfg.zfs.enable) {
       device = "/dev/disk/by-label/nixos";
       fsType = "ext4";
       autoResize = true;
     };
 
+    fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) {
+      # The ZFS image uses a partition labeled ESP whether or not we're
+      # booting with EFI.
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
+
     boot.growPartition = true;
-    boot.kernelParams = [ "console=ttyS0" ];
-    boot.loader.grub.device = "/dev/vda";
-    boot.loader.timeout = 0;
+    boot.kernelParams = [ "console=tty1" ];
+    boot.loader.grub.device = if (!cfg.efi) then "/dev/vda" else "nodev";
+    boot.loader.grub.efiSupport = cfg.efi;
+    boot.loader.grub.efiInstallAsRemovable = cfg.efi;
+    boot.loader.timeout = 1;
+    boot.loader.grub.extraConfig = ''
+      serial --unit=1 --speed=115200 --word=8 --parity=no --stop=1
+      terminal_output console serial
+      terminal_input console serial
+    '';
+
+    services.zfs.expandOnBoot = mkIf cfg.zfs.enable (lib.mkDefault "all");
+    boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/";
 
     # Allow root logins
     services.openssh = {
@@ -36,6 +63,11 @@ in
       passwordAuthentication = mkDefault false;
     };
 
+    users.users.root.initialPassword = "foobar";
+
+    # Enable the serial console on tty1
+    systemd.services."serial-getty@tty1".enable = true;
+
     # Force getting the hostname from Openstack metadata.
     networking.hostName = mkDefault "";
 
@@ -43,7 +75,7 @@ in
       path = [ pkgs.wget ];
       description = "Fetch Metadata on startup";
       wantedBy = [ "multi-user.target" ];
-      before = [ "apply-ec2-data.service" "amazon-init.service"];
+      before = [ "apply-ec2-data.service" "amazon-init.service" ];
       wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       script = metadataFetcher;
diff --git a/nixos/modules/virtualisation/openstack-metadata-fetcher.nix b/nixos/modules/virtualisation/openstack-metadata-fetcher.nix
index 25104bb4766..d62428b47a4 100644
--- a/nixos/modules/virtualisation/openstack-metadata-fetcher.nix
+++ b/nixos/modules/virtualisation/openstack-metadata-fetcher.nix
@@ -14,9 +14,9 @@
     wget ${wgetExtraOptions} "$@"
   }
 
-  wget_imds -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
+  wget_imds -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path || true
   # When no user-data is provided, the OpenStack metadata server doesn't expose the user-data route.
   (umask 077 && wget_imds -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data || rm -f "$metaDir/user-data")
-  wget_imds -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
-  wget_imds -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
+  wget_imds -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname || true
+  wget_imds -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key || true
 ''
diff --git a/nixos/modules/virtualisation/openstack-options.nix b/nixos/modules/virtualisation/openstack-options.nix
new file mode 100644
index 00000000000..cbc779f27c8
--- /dev/null
+++ b/nixos/modules/virtualisation/openstack-options.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) literalExpression types;
+in
+{
+  options = {
+    openstack = {
+      zfs = {
+        enable = lib.mkOption {
+          default = false;
+          internal = true;
+          description = ''
+            Whether the OpenStack instance uses a ZFS root.
+          '';
+        };
+
+        datasets = lib.mkOption {
+          description = ''
+            Datasets to create under the `tank` and `boot` zpools.
+
+            **NOTE:** This option is used only at image creation time, and
+            does not attempt to declaratively create or manage datasets
+            on an existing system.
+          '';
+
+          default = { };
+
+          type = types.attrsOf (types.submodule {
+            options = {
+              mount = lib.mkOption {
+                description = "Where to mount this dataset.";
+                type = types.nullOr types.string;
+                default = null;
+              };
+
+              properties = lib.mkOption {
+                description = "Properties to set on this dataset.";
+                type = types.attrsOf types.string;
+                default = { };
+              };
+            };
+          });
+        };
+      };
+
+      efi = lib.mkOption {
+        default = pkgs.stdenv.hostPlatform.isAarch64;
+        defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
+        internal = true;
+        description = ''
+          Whether the instance is using EFI.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf config.openstack.zfs.enable {
+    networking.hostId = lib.mkDefault "00000000";
+
+    fileSystems =
+      let
+        mountable = lib.filterAttrs (_: value: ((value.mount or null) != null)) config.openstack.zfs.datasets;
+      in
+      lib.mapAttrs'
+        (dataset: opts: lib.nameValuePair opts.mount {
+          device = dataset;
+          fsType = "zfs";
+        })
+        mountable;
+  };
+}
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 94fd727a4b5..b7e7f78ded7 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -6,7 +6,10 @@ let
 
   inherit (lib) mkOption types;
 
-  podmanPackage = (pkgs.podman.override { inherit (cfg) extraPackages; });
+  podmanPackage = (pkgs.podman.override {
+    extraPackages = cfg.extraPackages
+      ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
+  });
 
   # Provides a fake "docker" binary mapping to podman
   dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}" {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 51438935894..b1c5a7a6c95 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -796,7 +796,7 @@ in
     # allow `system.build.toplevel' to be included.  (If we had a direct
     # reference to ${regInfo} here, then we would get a cyclic
     # dependency.)
-    boot.postBootCommands =
+    boot.postBootCommands = lib.mkIf config.nix.enable
       ''
         if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then
           ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]}
@@ -853,8 +853,12 @@ in
       (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
         "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
       ])
-      (mkIf (!cfg.useBootLoader) [
-        "-kernel ${config.system.build.toplevel}/kernel"
+      (let
+        alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
+        # Replace all non-alphanumeric characters with underscores
+        sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s);
+      in mkIf (!cfg.useBootLoader) [
+        "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}"
         "-initrd ${config.system.build.toplevel}/initrd"
         ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
       ])
@@ -923,6 +927,8 @@ in
       mkVMOverride (cfg.fileSystems //
       {
         "/".device = cfg.bootDevice;
+        "/".fsType = "ext4";
+        "/".autoFormat = true;
 
         "/tmp" = mkIf config.boot.tmpOnTmpfs
           { device = "tmpfs";
@@ -953,6 +959,28 @@ in
           };
       } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
 
+    boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
+      mounts = [{
+        where = "/sysroot/nix/store";
+        what = "overlay";
+        type = "overlay";
+        options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
+        wantedBy = ["local-fs.target"];
+        before = ["local-fs.target"];
+        requires = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+        after = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+        unitConfig.IgnoreOnIsolate = true;
+      }];
+      services.rw-store = {
+        after = ["sysroot-nix-.rw\\x2dstore.mount"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+        };
+      };
+    };
+
     swapDevices = mkVMOverride [ ];
     boot.initrd.luks.devices = mkVMOverride {};
 
@@ -961,7 +989,10 @@ in
 
     services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
 
-    system.build.vm = pkgs.runCommand "nixos-vm" { preferLocalBuild = true; }
+    system.build.vm = pkgs.runCommand "nixos-vm" {
+      preferLocalBuild = true;
+      meta.mainProgram = "run-${config.system.name}-vm";
+    }
       ''
         mkdir -p $out/bin
         ln -s ${config.system.build.toplevel} $out/system
diff --git a/nixos/modules/virtualisation/waydroid.nix b/nixos/modules/virtualisation/waydroid.nix
index 4fc798ff39f..2c0b658948d 100644
--- a/nixos/modules/virtualisation/waydroid.nix
+++ b/nixos/modules/virtualisation/waydroid.nix
@@ -56,8 +56,6 @@ in
 
       wantedBy = [ "multi-user.target" ];
 
-      path = with pkgs; [ getent iptables iproute kmod nftables util-linux which ];
-
       unitConfig = {
         ConditionPathExists = "/var/lib/waydroid/lxc/waydroid";
       };
@@ -68,6 +66,10 @@ in
         ExecStopPost = "${pkgs.waydroid}/bin/waydroid session stop";
       };
     };
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
+    ];
   };
 
 }