about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/profiles/hardened.xml10
-rw-r--r--nixos/doc/manual/release-notes/rl-2103.xml48
-rw-r--r--nixos/modules/config/console.nix3
-rw-r--r--nixos/modules/config/update-users-groups.pl9
-rw-r--r--nixos/modules/config/users-groups.nix2
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix27
-rw-r--r--nixos/modules/hardware/opentabletdriver.nix67
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix30
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix32
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix2
-rw-r--r--nixos/modules/installer/tools/nixos-enter.sh3
-rw-r--r--nixos/modules/misc/documentation.nix2
-rw-r--r--nixos/modules/module-list.nix7
-rw-r--r--nixos/modules/profiles/hardened.nix7
-rw-r--r--nixos/modules/profiles/installation-device.nix4
-rw-r--r--nixos/modules/programs/command-not-found/command-not-found.pl15
-rw-r--r--nixos/modules/programs/fish.nix8
-rw-r--r--nixos/modules/programs/msmtp.nix104
-rw-r--r--nixos/modules/programs/ssmtp.nix13
-rw-r--r--nixos/modules/security/acme.xml2
-rw-r--r--nixos/modules/security/pam.nix2
-rw-r--r--nixos/modules/services/hardware/throttled.nix6
-rw-r--r--nixos/modules/services/hardware/trezord.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix4
-rw-r--r--nixos/modules/services/misc/matrix-appservice-discord.nix8
-rw-r--r--nixos/modules/services/monitoring/grafana-image-renderer.nix150
-rw-r--r--nixos/modules/services/monitoring/grafana.nix9
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix175
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bird.nix46
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/json.nix28
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix121
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix60
-rw-r--r--nixos/modules/services/networking/corerad.nix12
-rw-r--r--nixos/modules/services/networking/shellhub-agent.nix91
-rw-r--r--nixos/modules/services/ttys/getty.nix (renamed from nixos/modules/services/ttys/agetty.nix)20
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix16
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix9
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix31
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix3
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix183
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix58
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix4
-rw-r--r--nixos/modules/virtualisation/amazon-init.nix2
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix2
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix6
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix4
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/docker-tools.nix7
-rw-r--r--nixos/tests/dovecot.nix7
-rw-r--r--nixos/tests/git/hub.nix17
-rw-r--r--nixos/tests/grafana.nix14
-rw-r--r--nixos/tests/login.nix2
-rw-r--r--nixos/tests/loki.nix4
-rw-r--r--nixos/tests/postgresql-wal-receiver.nix16
-rw-r--r--nixos/tests/prometheus-exporters.nix61
-rw-r--r--nixos/tests/shadow.nix14
59 files changed, 1308 insertions, 298 deletions
diff --git a/nixos/doc/manual/configuration/profiles/hardened.xml b/nixos/doc/manual/configuration/profiles/hardened.xml
index dc83fc837e2a3..4a51754cc7ae2 100644
--- a/nixos/doc/manual/configuration/profiles/hardened.xml
+++ b/nixos/doc/manual/configuration/profiles/hardened.xml
@@ -7,7 +7,7 @@
 
  <para>
   A profile with most (vanilla) hardening options enabled by default,
-  potentially at the cost of features and performance.
+  potentially at the cost of stability, features and performance.
  </para>
 
  <para>
@@ -21,4 +21,12 @@
    xlink:href="https://github.com/nixos/nixpkgs/tree/master/nixos/modules/profiles/hardened.nix">
   profile source</literal> for further detail on which settings are altered.
  </para>
+ <warning>
+   <para>
+     This profile enables options that are known to affect system
+     stability. If you experience any stability issues when using the
+     profile, try disabling it. If you report an issue and use this
+     profile, always mention that you do.
+   </para>
+ </warning>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2103.xml b/nixos/doc/manual/release-notes/rl-2103.xml
index 1e3ae23b9b387..05daca1d710cb 100644
--- a/nixos/doc/manual/release-notes/rl-2103.xml
+++ b/nixos/doc/manual/release-notes/rl-2103.xml
@@ -169,6 +169,14 @@
     </para>
    </listitem>
    <listitem>
+     <para>
+       xfsprogs was update from 4.19 to 5.10. It now enables reflink support by default on filesystem creation.
+       Support for reflinks was added with an experimental status to kernel 4.9 and deemed stable in kernel 4.16.
+       If you want to be able to mount XFS filesystems created with this release of xfsprogs on kernel releases older than those, you need to format them
+       with <literal>mkfs.xfs -m reflink=0</literal>.
+     </para>
+   </listitem>
+   <listitem>
     <para>
     <package>btc1</package> has been abandoned upstream, and removed.
     </para>
@@ -299,12 +307,52 @@
    </listitem>
    <listitem>
      <para>
+       The <literal>wafHook</literal> hook does not wrap Python anymore.
+       Packages depending on <literal>wafHook</literal> need to include any Python into their <literal>nativeBuildInputs</literal>.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
        Starting with version 1.7.0, the project formerly named <literal>CodiMD</literal>
        is now named <literal>HedgeDoc</literal>.
        New installations will no longer use the old name for users, state directories and such, this needs to be considered when moving state to a more recent NixOS installation.
        Based on <xref linkend="opt-system.stateVersion" />, existing installations will continue to work.
      </para>
    </listitem>
+   <listitem>
+    <para>
+     <package>fish-foreign-env</package> is now an alias for the
+     <package>fishPlugins.foreign-env</package> package, in which the fish
+     functions have been relocated to the
+     <literal>vendor_functions.d</literal> directory to be loaded automatically.
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+       The prometheus json exporter is now managed by the prometheus community. Together with additional features
+       some backwards incompatibilities were introduced.
+       Most importantly the exporter no longer accepts a fixed command-line parameter to specify the URL of the
+       endpoint serving JSON. It now expects this URL to be passed as an URL parameter, when scraping the exporter's
+       <literal>/probe</literal> endpoint.
+       In the prometheus scrape configuration the scrape target might look like this:
+       <programlisting>
+http://some.json-exporter.host:7979/probe?target=https://example.com/some/json/endpoint
+       </programlisting>
+     </para>
+     <para>
+       Existing configuration for the exporter needs to be updated, but can partially be re-used.
+       Documentation is available in the upstream repository and a small example for NixOS is available
+       in the corresponding NixOS test.
+     </para>
+     <para>
+       These changes also affect <xref linkend="opt-services.prometheus.exporters.rspamd.enable" />, which is
+       just a preconfigured instance of the json exporter.
+     </para>
+     <para>
+       For more information, take a look at the <link xlink:href="https://github.com/prometheus-community/json_exporter">
+       official documentation</link> of the json_exporter.
+     </para>
+   </listitem>
   </itemizedlist>
  </section>
 
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index b8d52d1a8fc7c..ab16676051779 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -145,7 +145,8 @@ in
         '';
 
         systemd.services.systemd-vconsole-setup =
-          { before = [ "display-manager.service" ];
+          {
+            before = optional config.services.xserver.enable "display-manager.service";
             after = [ "systemd-udev-settle.service" ];
             restartTriggers = [ vconsoleConf consoleEnv ];
           };
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 7582371529321..44040217b0271 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -227,6 +227,15 @@ foreach my $u (@{$spec->{users}}) {
         $u->{hashedPassword} = hashPassword($u->{password});
     }
 
+    if (!defined $u->{shell}) {
+        if (defined $existing) {
+            $u->{shell} = $existing->{shell};
+        } else {
+            warn "warning: no declarative or previous shell for ‘$name’, setting shell to nologin\n";
+            $u->{shell} = "/run/current-system/sw/bin/nologin";
+        }
+    }
+
     $u->{fakePassword} = $existing->{fakePassword} // "x";
     $usersOut{$name} = $u;
 
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index a957633809864..e90a7d567d42c 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -153,7 +153,7 @@ let
       };
 
       shell = mkOption {
-        type = types.either types.shellPackage types.path;
+        type = types.nullOr (types.either types.shellPackage types.path);
         default = pkgs.shadow;
         defaultText = "pkgs.shadow";
         example = literalExample "pkgs.bashInteractive";
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
new file mode 100644
index 0000000000000..5cb09e5af499f
--- /dev/null
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkOption mkIf types;
+  cfg = config.hardware.keyboard.zsa;
+in
+{
+  # TODO: make group configurable like in https://github.com/NixOS/nixpkgs/blob/0b2b4b8c4e729535a61db56468809c5c2d3d175c/pkgs/tools/security/nitrokey-app/udev-rules.nix ?
+  options.hardware.keyboard.zsa = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
+        You need it when you want to flash a new configuration on the keyboard
+        or use their live training in the browser.
+        Access to the keyboard is granted to users in the "plugdev" group.
+        You may want to install the wally-cli package.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.zsa-udev-rules ];
+    users.groups.plugdev = {};
+  };
+}
diff --git a/nixos/modules/hardware/opentabletdriver.nix b/nixos/modules/hardware/opentabletdriver.nix
new file mode 100644
index 0000000000000..0f34d903f68bc
--- /dev/null
+++ b/nixos/modules/hardware/opentabletdriver.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.hardware.opentabletdriver;
+in
+{
+  options = {
+    hardware.opentabletdriver = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable OpenTabletDriver udev rules, user service and blacklist kernel
+          modules known to conflict with OpenTabletDriver.
+        '';
+      };
+
+      blacklistedKernelModules = mkOption {
+        type = types.listOf types.str;
+        default = [ "hid-uclogic" "wacom" ];
+        description = ''
+          Blacklist of kernel modules known to conflict with OpenTabletDriver.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.opentabletdriver;
+        defaultText = "pkgs.opentabletdriver";
+        description = ''
+          OpenTabletDriver derivation to use.
+        '';
+      };
+
+      daemon = {
+        enable = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Whether to start OpenTabletDriver daemon as a systemd user service.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    services.udev.packages = [ cfg.package ];
+
+    boot.blacklistedKernelModules = cfg.blacklistedKernelModules;
+
+    systemd.user.services.opentabletdriver = with pkgs; mkIf cfg.daemon.enable {
+      description = "Open source, cross-platform, user-mode tablet driver";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/otd-daemon -c ${cfg.package}/lib/OpenTabletDriver/Configurations";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index a5bc436be8262..e4ec2d6240d02 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -17,8 +17,7 @@
   # The serial ports listed here are:
   # - ttyS0: for Tegra (Jetson TX1)
   # - ttyAMA0: for QEMU's -machine virt
-  # Also increase the amount of CMA to ensure the virtual console on the RPi3 works.
-  boot.kernelParams = ["cma=32M" "console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
+  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
 
   boot.initrd.availableKernelModules = [
     # Allows early (earlier) modesetting for the Raspberry Pi
@@ -30,13 +29,25 @@
   sdImage = {
     populateFirmwareCommands = let
       configTxt = pkgs.writeText "config.txt" ''
+        [pi3]
         kernel=u-boot-rpi3.bin
 
+        [pi4]
+        kernel=u-boot-rpi4.bin
+        enable_gic=1
+        armstub=armstub8-gic.bin
+
+        # Otherwise the resolution will be weird in most cases, compared to
+        # what the pi3 firmware does by default.
+        disable_overscan=1
+
+        [all]
         # Boot in 64-bit mode.
         arm_64bit=1
 
-        # U-Boot used to need this to work, regardless of whether UART is actually used or not.
-        # TODO: check when/if this can be removed.
+        # U-Boot needs this to work, regardless of whether UART is actually used or not.
+        # Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
+        # a requirement in the future.
         enable_uart=1
 
         # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
@@ -45,8 +56,17 @@
       '';
       in ''
         (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
-        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+
+        # Add the config
         cp ${configTxt} firmware/config.txt
+
+        # Add pi3 specific files
+        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+
+        # Add pi4 specific files
+        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/
       '';
     populateRootCommands = ''
       mkdir -p ./files/boot
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
index 87545e8420308..5bdec7de86e8a 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
@@ -3,36 +3,6 @@
 { config, lib, pkgs, ... }:
 
 {
-  imports = [
-    ../../profiles/base.nix
-    ../../profiles/installation-device.nix
-    ./sd-image.nix
-  ];
-
-  boot.loader.grub.enable = false;
-  boot.loader.raspberryPi.enable = true;
-  boot.loader.raspberryPi.version = 4;
+  imports = [ ./sd-image-aarch64.nix ];
   boot.kernelPackages = pkgs.linuxPackages_rpi4;
-
-  boot.consoleLogLevel = lib.mkDefault 7;
-
-  sdImage = {
-    firmwareSize = 128;
-    firmwarePartitionName = "NIXOS_BOOT";
-    # This is a hack to avoid replicating config.txt from boot.loader.raspberryPi
-    populateFirmwareCommands =
-      "${config.system.build.installBootLoader} ${config.system.build.toplevel} -d ./firmware";
-    # As the boot process is done entirely in the firmware partition.
-    populateRootCommands = "";
-  };
-
-  fileSystems."/boot/firmware" = {
-    # This effectively "renames" the attrsOf entry set in sd-image.nix
-    mountPoint = "/boot";
-    neededForBoot = true;
-  };
-
-  # the installation media is also the installation target,
-  # so we don't want to provide the installation configuration.nix.
-  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
index 6d4ba96dba0c1..8159576a62ac7 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
@@ -104,7 +104,7 @@ in
     '';
 
   # Some more help text.
-  services.mingetty.helpLine =
+  services.getty.helpLine =
     ''
 
       Log in as "root" with an empty password.  ${
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 0e67ae7de6980..95579f3ca06d2 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -122,7 +122,7 @@ in
       device = "/dev/something";
     };
 
-  services.mingetty = {
+  services.getty = {
     # Some more help text.
     helpLine = ''
       Log in as "root" with an empty password.  ${
diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh
index c72ef6e9c28b3..450d776181489 100644
--- a/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixos/modules/installer/tools/nixos-enter.sh
@@ -69,6 +69,9 @@ mount --rbind /sys "$mountPoint/sys"
 
     # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings.
     LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" 1>&2 || true
+
+    # Create /tmp
+    chroot "$mountPoint" systemd-tmpfiles --create --remove --exclude-prefix=/dev 1>&2 || true
 )
 
 exec chroot "$mountPoint" "${command[@]}"
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index fe0263f158f43..d81d6c6cb9b8c 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -261,7 +261,7 @@ in
         ++ optionals cfg.doc.enable ([ manual.manualHTML nixos-help ]
            ++ optionals config.services.xserver.enable [ pkgs.nixos-icons ]);
 
-      services.mingetty.helpLine = mkIf cfg.doc.enable (
+      services.getty.helpLine = mkIf cfg.doc.enable (
           "\nRun 'nixos-help' for the NixOS manual."
       );
     })
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 3b67a8574938c..8fd5d4519fdd8 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -47,6 +47,7 @@
   ./hardware/digitalbitbox.nix
   ./hardware/device-tree.nix
   ./hardware/sensor/iio.nix
+  ./hardware/keyboard/zsa.nix
   ./hardware/ksm.nix
   ./hardware/ledger.nix
   ./hardware/logitech.nix
@@ -65,6 +66,7 @@
   ./hardware/tuxedo-keyboard.nix
   ./hardware/usb-wwan.nix
   ./hardware/onlykey.nix
+  ./hardware/opentabletdriver.nix
   ./hardware/wooting.nix
   ./hardware/uinput.nix
   ./hardware/video/amdgpu.nix
@@ -140,6 +142,7 @@
   ./programs/light.nix
   ./programs/mosh.nix
   ./programs/mininet.nix
+  ./programs/msmtp.nix
   ./programs/mtr.nix
   ./programs/nano.nix
   ./programs/neovim.nix
@@ -537,6 +540,7 @@
   ./services/monitoring/do-agent.nix
   ./services/monitoring/fusion-inventory.nix
   ./services/monitoring/grafana.nix
+  ./services/monitoring/grafana-image-renderer.nix
   ./services/monitoring/grafana-reporter.nix
   ./services/monitoring/graphite.nix
   ./services/monitoring/hdaps.nix
@@ -742,6 +746,7 @@
   ./services/networking/skydns.nix
   ./services/networking/shadowsocks.nix
   ./services/networking/shairport-sync.nix
+  ./services/networking/shellhub-agent.nix
   ./services/networking/shorewall.nix
   ./services/networking/shorewall6.nix
   ./services/networking/shout.nix
@@ -847,7 +852,7 @@
   ./services/torrent/peerflix.nix
   ./services/torrent/rtorrent.nix
   ./services/torrent/transmission.nix
-  ./services/ttys/agetty.nix
+  ./services/ttys/getty.nix
   ./services/ttys/gpm.nix
   ./services/ttys/kmscon.nix
   ./services/wayland/cage.nix
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 42ed62b063044..680fa40b91195 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -1,5 +1,10 @@
 # A profile with most (vanilla) hardening options enabled by default,
-# potentially at the cost of features and performance.
+# potentially at the cost of stability, features and performance.
+#
+# This profile enables options that are known to affect system
+# stability. If you experience any stability issues when using the
+# profile, try disabling it. If you report an issue and use this
+# profile, always mention that you do.
 
 { config, lib, pkgs, ... }:
 
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index e68ea1b08776a..7dc493fb495da 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -45,10 +45,10 @@ with lib;
     };
 
     # Automatically log in at the virtual consoles.
-    services.mingetty.autologinUser = "nixos";
+    services.getty.autologinUser = "nixos";
 
     # Some more help text.
-    services.mingetty.helpLine = ''
+    services.getty.helpLine = ''
       The "nixos" and "root" accounts have empty passwords.
 
       An ssh daemon is running. You then must set a password
diff --git a/nixos/modules/programs/command-not-found/command-not-found.pl b/nixos/modules/programs/command-not-found/command-not-found.pl
index ab7aa204653cd..7515dd975c316 100644
--- a/nixos/modules/programs/command-not-found/command-not-found.pl
+++ b/nixos/modules/programs/command-not-found/command-not-found.pl
@@ -27,8 +27,8 @@ if (!defined $res || scalar @$res == 0) {
     my $package = @$res[0]->{package};
     if ($ENV{"NIX_AUTO_INSTALL"} // "") {
         print STDERR <<EOF;
-The program ‘$program’ is currently not installed. It is provided by
-the package ‘$package’, which I will now install for you.
+The program '$program' is currently not installed. It is provided by
+the package '$package', which I will now install for you.
 EOF
         ;
         exit 126 if system("nix-env", "-iA", "nixos.$package") == 0;
@@ -36,16 +36,17 @@ EOF
         exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV));
     } else {
         print STDERR <<EOF;
-The program ‘$program’ is currently not installed. You can install it by typing:
-  nix-env -iA nixos.$package
+The program '$program' is not in your PATH. You can make it available in an
+ephemeral shell by typing:
+  nix-shell -p $package
 EOF
     }
 } else {
     print STDERR <<EOF;
-The program ‘$program’ is currently not installed. It is provided by
-several packages. You can install it by typing one of the following:
+The program '$program' is not in your PATH. It is provided by several packages.
+You can make it available in an ephemeral shell by typing one of the following:
 EOF
-    print STDERR "  nix-env -iA nixos.$_->{package}\n" foreach @$res;
+    print STDERR "  nix-shell -p $_->{package}\n" foreach @$res;
 }
 
 exit 127;
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 50d1077dd4108..34a0dc6a2df3d 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -112,7 +112,7 @@ in
     environment.etc."fish/nixos-env-preinit.fish".text = ''
       # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently
       # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish
-      set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions
+      set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions
 
       # source the NixOS environment config
       if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]
@@ -128,7 +128,7 @@ in
 
       # if we haven't sourced the general config, do it
       if not set -q __fish_nixos_general_config_sourced
-        set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+        set --prepend fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d
         fenv source /etc/fish/foreign-env/shellInit > /dev/null
         set -e fish_function_path[1]
 
@@ -142,7 +142,7 @@ in
       # if we haven't sourced the login config, do it
       status --is-login; and not set -q __fish_nixos_login_config_sourced
       and begin
-        set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+        set --prepend fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d
         fenv source /etc/fish/foreign-env/loginShellInit > /dev/null
         set -e fish_function_path[1]
 
@@ -158,7 +158,7 @@ in
       and begin
         ${fishAliases}
 
-        set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+        set --prepend fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d
         fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null
         set -e fish_function_path[1]
 
diff --git a/nixos/modules/programs/msmtp.nix b/nixos/modules/programs/msmtp.nix
new file mode 100644
index 0000000000000..217060e6b3b32
--- /dev/null
+++ b/nixos/modules/programs/msmtp.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.msmtp;
+
+in {
+  meta.maintainers = with maintainers; [ pacien ];
+
+  options = {
+    programs.msmtp = {
+      enable = mkEnableOption "msmtp - an SMTP client";
+
+      setSendmail = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to set the system sendmail to msmtp's.
+        '';
+      };
+
+      defaults = mkOption {
+        type = types.attrs;
+        default = {};
+        example = {
+          aliases = "/etc/aliases";
+          port = 587;
+          tls = true;
+        };
+        description = ''
+          Default values applied to all accounts.
+          See msmtp(1) for the available options.
+        '';
+      };
+
+      accounts = mkOption {
+        type = with types; attrsOf attrs;
+        default = {};
+        example = {
+          "default" = {
+            host = "smtp.example";
+            auth = true;
+            user = "someone";
+            passwordeval = "cat /secrets/password.txt";
+          };
+        };
+        description = ''
+          Named accounts and their respective configurations.
+          The special name "default" allows a default account to be defined.
+          See msmtp(1) for the available options.
+
+          Use `programs.msmtp.extraConfig` instead of this attribute set-based
+          option if ordered account inheritance is needed.
+
+          It is advised to use the `passwordeval` setting to read the password
+          from a secret file to avoid having it written in the world-readable
+          nix store. The password file must end with a newline (`\n`).
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to add to the msmtp configuration verbatim.
+          See msmtp(1) for the syntax and available options.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.msmtp ];
+
+    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail {
+      program = "sendmail";
+      source = "${pkgs.msmtp}/bin/sendmail";
+      setuid = false;
+      setgid = false;
+    };
+
+    environment.etc."msmtprc".text = let
+      mkValueString = v:
+        if v == true then "on"
+        else if v == false then "off"
+        else generators.mkValueStringDefault {} v;
+      mkKeyValueString = k: v: "${k} ${mkValueString v}";
+      mkInnerSectionString =
+        attrs: concatStringsSep "\n" (mapAttrsToList mkKeyValueString attrs);
+      mkAccountString = name: attrs: ''
+        account ${name}
+        ${mkInnerSectionString attrs}
+      '';
+    in ''
+      defaults
+      ${mkInnerSectionString cfg.defaults}
+
+      ${concatStringsSep "\n" (mapAttrsToList mkAccountString cfg.accounts)}
+
+      ${cfg.extraConfig}
+    '';
+  };
+}
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
index 1f49ddc91bb38..8039763faccc9 100644
--- a/nixos/modules/programs/ssmtp.nix
+++ b/nixos/modules/programs/ssmtp.nix
@@ -162,15 +162,16 @@ in
       (mkIf (cfg.authPassFile != null) { AuthPassFile = cfg.authPassFile; })
     ];
 
-    environment.etc."ssmtp/ssmtp.conf".source =
-      let
-        toStr = value:
+    # 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 builtins.toString value
+          else mkValueStringDefault {} value
         ;
-      in
-        pkgs.writeText "ssmtp.conf" (concatStringsSep "\n" (mapAttrsToList (key: value: "${key}=${toStr value}") cfg.settings));
+      } "=";
+    } cfg.settings;
 
     environment.systemPackages = [pkgs.ssmtp];
 
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index 517162d1a7bfa..f248112917284 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -268,7 +268,7 @@ chmod 400 /var/lib/secrets/certs.secret
 
   <para>
    It is possible that your account credentials file may become corrupt and need
-   to be regnerated. In this scenario lego will produce the error <literal>JWS verification error</literal>.
+   to be regenerated. In this scenario lego will produce the error <literal>JWS verification error</literal>.
    The solution is to simply delete the associated accounts file and
    re-run the affected service(s).
   </para>
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index a428103eaa963..1522111dbddfa 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -394,7 +394,7 @@ let
           ${optionalString cfg.requireWheel
               "auth required pam_wheel.so use_uid"}
           ${optionalString cfg.logFailures
-              "auth required pam_tally.so"}
+              "auth required pam_faillock.so"}
           ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth)
               "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}"}
           ${optionalString cfg.fprintAuth
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index 7617c4492d7c6..1905eb565c6dc 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -26,5 +26,11 @@ in {
       if cfg.extraConfig != ""
       then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
       else "${pkgs.throttled}/etc/lenovo_fix.conf";
+
+    # Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
+    # See https://github.com/erpalma/throttled/issues/215
+    boot.kernelParams =
+      optional (versionAtLeast config.boot.kernelPackages.kernel.version "5.9")
+      "msr.allow_writes=on";
   };
 }
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index 2594ac7437109..8c609bbf825b6 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -47,7 +47,7 @@ in {
     services.udev.packages = [ pkgs.trezor-udev-rules ];
 
     systemd.services.trezord = {
-      description = "TREZOR Bridge";
+      description = "Trezor Bridge";
       after = [ "systemd-udev-settle.service" "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [];
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 3ee7a81dc3753..35a9dccdff284 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -44,6 +44,9 @@ let
     [gitlab-shell]
     dir = "${cfg.packages.gitlab-shell}"
 
+    [hooks]
+    custom_hooks_dir = "${cfg.statePath}/custom_hooks"
+
     [gitlab]
     secret_file = "${cfg.statePath}/gitlab_shell_secret"
     url = "http+unix://${pathUrlQuote gitlabSocket}"
@@ -65,7 +68,6 @@ let
     repos_path = "${cfg.statePath}/repositories";
     secret_file = "${cfg.statePath}/gitlab_shell_secret";
     log_file = "${cfg.statePath}/log/gitlab-shell.log";
-    custom_hooks_dir = "${cfg.statePath}/custom_hooks";
     redis = {
       bin = "${pkgs.redis}/bin/redis-cli";
       host = "127.0.0.1";
diff --git a/nixos/modules/services/misc/matrix-appservice-discord.nix b/nixos/modules/services/misc/matrix-appservice-discord.nix
index 49c41ff637a8f..71d1227f4ff72 100644
--- a/nixos/modules/services/misc/matrix-appservice-discord.nix
+++ b/nixos/modules/services/misc/matrix-appservice-discord.nix
@@ -5,7 +5,7 @@ with lib;
 let
   dataDir = "/var/lib/matrix-appservice-discord";
   registrationFile = "${dataDir}/discord-registration.yaml";
-  appDir = "${pkgs.matrix-appservice-discord}/lib/node_modules/matrix-appservice-discord";
+  appDir = "${pkgs.matrix-appservice-discord}/${pkgs.matrix-appservice-discord.passthru.nodeAppDir}";
   cfg = config.services.matrix-appservice-discord;
   # TODO: switch to configGen.json once RFC42 is implemented
   settingsFile = pkgs.writeText "matrix-appservice-discord-settings.json" (builtins.toJSON cfg.settings);
@@ -22,12 +22,6 @@ in {
         default = {
           database = {
             filename = "${dataDir}/discord.db";
-
-            # TODO: remove those old config keys once the following issues are solved:
-            # * https://github.com/Half-Shot/matrix-appservice-discord/issues/490
-            # * https://github.com/Half-Shot/matrix-appservice-discord/issues/498
-            userStorePath = "${dataDir}/user-store.db";
-            roomStorePath = "${dataDir}/room-store.db";
           };
 
           # empty values necessary for registration file generation
diff --git a/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixos/modules/services/monitoring/grafana-image-renderer.nix
new file mode 100644
index 0000000000000..a010a5316baeb
--- /dev/null
+++ b/nixos/modules/services/monitoring/grafana-image-renderer.nix
@@ -0,0 +1,150 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grafana-image-renderer;
+
+  format = pkgs.formats.json { };
+
+  configFile = format.generate "grafana-image-renderer-config.json" cfg.settings;
+in {
+  options.services.grafana-image-renderer = {
+    enable = mkEnableOption "grafana-image-renderer";
+
+    chromium = mkOption {
+      type = types.package;
+      description = ''
+        The chromium to use for image rendering.
+      '';
+    };
+
+    verbose = mkEnableOption "verbosity for the service";
+
+    provisionGrafana = mkEnableOption "Grafana configuration for grafana-image-renderer";
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = format.type;
+
+        options = {
+          service = {
+            port = mkOption {
+              type = types.port;
+              default = 8081;
+              description = ''
+                The TCP port to use for the rendering server.
+              '';
+            };
+            logging.level = mkOption {
+              type = types.enum [ "error" "warning" "info" "debug" ];
+              default = "info";
+              description = ''
+                The log-level of the <filename>grafana-image-renderer.service</filename>-unit.
+              '';
+            };
+          };
+          rendering = {
+            width = mkOption {
+              default = 1000;
+              type = types.ints.positive;
+              description = ''
+                Width of the PNG used to display the alerting graph.
+              '';
+            };
+            height = mkOption {
+              default = 500;
+              type = types.ints.positive;
+              description = ''
+                Height of the PNG used to display the alerting graph.
+              '';
+            };
+            mode = mkOption {
+              default = "default";
+              type = types.enum [ "default" "reusable" "clustered" ];
+              description = ''
+                Rendering mode of <package>grafana-image-renderer</package>:
+                <itemizedlist>
+                <listitem><para><literal>default:</literal> Creates on browser-instance
+                  per rendering request.</para></listitem>
+                <listitem><para><literal>reusable:</literal> One browser instance
+                  will be started and reused for each rendering request.</para></listitem>
+                <listitem><para><literal>clustered:</literal> allows to precisely
+                  configure how many browser-instances are supposed to be used. The values
+                  for that mode can be declared in <literal>rendering.clustering</literal>.
+                  </para></listitem>
+                </itemizedlist>
+              '';
+            };
+            args = mkOption {
+              type = types.listOf types.str;
+              default = [ "--no-sandbox" ];
+              description = ''
+                List of CLI flags passed to <package>chromium</package>.
+              '';
+            };
+          };
+        };
+      };
+
+      default = {};
+
+      description = ''
+        Configuration attributes for <package>grafana-image-renderer</package>.
+
+        See <link xlink:href="https://github.com/grafana/grafana-image-renderer/blob/ce1f81438e5f69c7fd7c73ce08bab624c4c92e25/default.json" />
+        for supported values.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.provisionGrafana -> config.services.grafana.enable;
+        message = ''
+          To provision a Grafana instance to use grafana-image-renderer,
+          `services.grafana.enable` must be set to `true`!
+        '';
+      }
+    ];
+
+    services.grafana.extraOptions = mkIf cfg.provisionGrafana {
+      RENDERING_SERVER_URL = "http://localhost:${toString cfg.settings.service.port}/render";
+      RENDERING_CALLBACK_URL = "http://localhost:${toString config.services.grafana.port}";
+    };
+
+    services.grafana-image-renderer.chromium = mkDefault pkgs.chromium;
+
+    services.grafana-image-renderer.settings = {
+      rendering = mapAttrs (const mkDefault) {
+        chromeBin = "${cfg.chromium}/bin/chromium";
+        verboseLogging = cfg.verbose;
+        timezone = config.time.timeZone;
+      };
+
+      services = {
+        logging.level = mkIf cfg.verbose (mkDefault "debug");
+        metrics.enabled = mkDefault false;
+      };
+    };
+
+    systemd.services.grafana-image-renderer = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = " A Grafana backend plugin that handles rendering of panels & dashboards to PNGs using headless browser (Chromium/Chrome)";
+
+      environment = {
+        PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = "true";
+      };
+
+      serviceConfig = {
+        DynamicUser = true;
+        PrivateTmp = true;
+        ExecStart = "${pkgs.grafana-image-renderer}/bin/grafana-image-renderer server --config=${configFile}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ ma27 ];
+}
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index b0c81a46d4d81..c8515c4b8988c 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -5,10 +5,11 @@ with lib;
 let
   cfg = config.services.grafana;
   opt = options.services.grafana;
+  declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
 
   envOptions = {
     PATHS_DATA = cfg.dataDir;
-    PATHS_PLUGINS = "${cfg.dataDir}/plugins";
+    PATHS_PLUGINS = if builtins.isNull cfg.declarativePlugins then "${cfg.dataDir}/plugins" else declarativePlugins;
     PATHS_LOGS = "${cfg.dataDir}/log";
 
     SERVER_PROTOCOL = cfg.protocol;
@@ -260,6 +261,12 @@ in {
       defaultText = "pkgs.grafana";
       type = types.package;
     };
+    declarativePlugins = mkOption {
+      type = with types; nullOr (listOf path);
+      default = null;
+      description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
+      example = literalExample "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
+    };
 
     dataDir = mkOption {
       description = "Data directory.";
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 72428957109c6..4f9be38f7f14a 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -32,6 +32,8 @@ let
       (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
     ]);
     scrape_configs = filterValidPrometheus cfg.scrapeConfigs;
+    remote_write = filterValidPrometheus cfg.remoteWrite;
+    remote_read = filterValidPrometheus cfg.remoteRead;
     alerting = {
       inherit (cfg) alertmanagers;
     };
@@ -101,6 +103,157 @@ let
     };
   };
 
+  promTypes.remote_read = types.submodule {
+    options = {
+      url = mkOption {
+        type = types.str;
+        description = ''
+          ServerName extension to indicate the name of the server.
+          http://tools.ietf.org/html/rfc4366#section-3.1
+        '';
+      };
+      name = mkOpt types.string ''
+        Name of the remote read config, which if specified must be unique among remote read configs.
+        The name will be used in metrics and logging in place of a generated value to help users distinguish between
+        remote read configs.
+      '';
+      required_matchers = mkOpt (types.attrsOf types.str) ''
+        An optional list of equality matchers which have to be
+        present in a selector to query the remote read endpoint.
+      '';
+      remote_timeout = mkOpt types.str ''
+        Timeout for requests to the remote read endpoint.
+      '';
+      read_recent = mkOpt types.bool ''
+        Whether reads should be made for queries for time ranges that
+        the local storage should have complete data for.
+      '';
+      basic_auth = mkOpt (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.str;
+            description = ''
+              HTTP username
+            '';
+          };
+          password = mkOpt types.str "HTTP password";
+          password_file = mkOpt types.str "HTTP password file";
+        };
+      }) ''
+        Sets the `Authorization` header on every remote read request with the
+        configured username and password.
+        password and password_file are mutually exclusive.
+      '';
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every remote read request with
+        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+      '';
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every remote read request with the bearer token
+        read from the configured file. It is mutually exclusive with `bearer_token`.
+      '';
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the remote read request's TLS settings.
+      '';
+      proxy_url = mkOpt types.str "Optional Proxy URL.";
+    };
+  };
+
+  promTypes.remote_write = types.submodule {
+    options = {
+      url = mkOption {
+        type = types.str;
+        description = ''
+          ServerName extension to indicate the name of the server.
+          http://tools.ietf.org/html/rfc4366#section-3.1
+        '';
+      };
+      remote_timeout = mkOpt types.str ''
+        Timeout for requests to the remote write endpoint.
+      '';
+      write_relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
+        List of remote write relabel configurations.
+      '';
+      name = mkOpt types.string ''
+        Name of the remote write config, which if specified must be unique among remote write configs.
+        The name will be used in metrics and logging in place of a generated value to help users distinguish between
+        remote write configs.
+      '';
+      basic_auth = mkOpt (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.str;
+            description = ''
+              HTTP username
+            '';
+          };
+          password = mkOpt types.str "HTTP password";
+          password_file = mkOpt types.str "HTTP password file";
+        };
+      }) ''
+        Sets the `Authorization` header on every remote write request with the
+        configured username and password.
+        password and password_file are mutually exclusive.
+      '';
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every remote write request with
+        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+      '';
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every remote write request with the bearer token
+        read from the configured file. It is mutually exclusive with `bearer_token`.
+      '';
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the remote write request's TLS settings.
+      '';
+      proxy_url = mkOpt types.str "Optional Proxy URL.";
+      queue_config = mkOpt (types.submodule {
+        options = {
+          capacity = mkOpt types.int ''
+            Number of samples to buffer per shard before we block reading of more
+            samples from the WAL. It is recommended to have enough capacity in each
+            shard to buffer several requests to keep throughput up while processing
+            occasional slow remote requests.
+          '';
+          max_shards = mkOpt types.int ''
+            Maximum number of shards, i.e. amount of concurrency.
+          '';
+          min_shards = mkOpt types.int ''
+            Minimum number of shards, i.e. amount of concurrency.
+          '';
+          max_samples_per_send = mkOpt types.int ''
+            Maximum number of samples per send.
+          '';
+          batch_send_deadline = mkOpt types.str ''
+            Maximum time a sample will wait in buffer.
+          '';
+          min_backoff = mkOpt types.str ''
+            Initial retry delay. Gets doubled for every retry.
+          '';
+          max_backoff = mkOpt types.str ''
+            Maximum retry delay.
+          '';
+        };
+      }) ''
+        Configures the queue used to write to remote storage.
+      '';
+      metadata_config = mkOpt (types.submodule {
+        options = {
+          send = mkOpt types.bool ''
+            Whether metric metadata is sent to remote storage or not.
+          '';
+          send_interval = mkOpt types.str ''
+            How frequently metric metadata is sent to remote storage.
+          '';
+        };
+      }) ''
+        Configures the sending of series metadata to remote storage.
+        Metadata configuration is subject to change at any point
+        or be removed in future releases.
+      '';
+    };
+  };
+
   promTypes.scrape_config = types.submodule {
     options = {
       job_name = mkOption {
@@ -432,10 +585,10 @@ let
         regular expression matches.
       '';
 
-      action = mkDefOpt (types.enum ["replace" "keep" "drop"]) "replace" ''
+      action =
+        mkDefOpt (types.enum ["replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep"]) "replace" ''
         Action to perform based on regex matching.
       '';
-
     };
   };
 
@@ -580,6 +733,24 @@ in {
       '';
     };
 
+    remoteRead = mkOption {
+      type = types.listOf promTypes.remote_read;
+      default = [];
+      description = ''
+        Parameters of the endpoints to query from.
+        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read">the official documentation</link> for more information.
+      '';
+    };
+
+    remoteWrite = mkOption {
+      type = types.listOf promTypes.remote_write;
+      default = [];
+      description = ''
+        Parameters of the endpoints to send samples to.
+        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write">the official documentation</link> for more information.
+      '';
+    };
+
     rules = mkOption {
       type = types.listOf types.str;
       default = [];
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 683dc0390c59b..1fd85c66f843d 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -23,6 +23,7 @@ let
   exporterOpts = genAttrs [
     "apcupsd"
     "bind"
+    "bird"
     "blackbox"
     "collectd"
     "dnsmasq"
@@ -47,6 +48,7 @@ let
     "rspamd"
     "rtl_433"
     "snmp"
+    "smokeping"
     "sql"
     "surfboard"
     "tor"
@@ -234,8 +236,6 @@ in
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
     services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey;
-  })] ++ [(mkIf config.services.rspamd.enable {
-    services.prometheus.exporters.rspamd.url = mkDefault "http://localhost:11334/stat";
   })] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable {
     hardware.rtl-sdr.enable = mkDefault true;
   })] ++ [(mkIf config.services.nginx.enable {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
new file mode 100644
index 0000000000000..d8a526eafcea9
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.bird;
+in
+{
+  port = 9324;
+  extraOpts = {
+    birdVersion = mkOption {
+      type = types.enum [ 1 2 ];
+      default = 2;
+      description = ''
+        Specifies whether BIRD1 or BIRD2 is in use.
+      '';
+    };
+    birdSocket = mkOption {
+      type = types.path;
+      default = "/var/run/bird.ctl";
+      description = ''
+        Path to BIRD2 (or BIRD1 v4) socket.
+      '';
+    };
+    newMetricFormat = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Enable the new more-generic metric format.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      SupplementaryGroups = singleton (if cfg.birdVersion == 1 then "bird" else "bird2");
+      ExecStart = ''
+        ${pkgs.prometheus-bird-exporter}/bin/bird_exporter \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          -bird.socket ${cfg.birdSocket} \
+          -bird.v2=${if cfg.birdVersion == 2 then "true" else "false"} \
+          -format.new=${if cfg.newMetricFormat then "true" else "false"} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
index bd0026b55f721..1800da69a2558 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -8,28 +8,36 @@ in
 {
   port = 7979;
   extraOpts = {
-    url = mkOption {
-      type = types.str;
-      description = ''
-        URL to scrape JSON from.
-      '';
-    };
     configFile = mkOption {
       type = types.path;
       description = ''
         Path to configuration file.
       '';
     };
-    listenAddress = {}; # not used
   };
   serviceOpts = {
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
-          --port ${toString cfg.port} \
-          ${cfg.url} ${escapeShellArg cfg.configFile} \
+        ${pkgs.prometheus-json-exporter}/bin/json_exporter \
+          --config.file ${escapeShellArg cfg.configFile} \
+          --web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
   };
+  imports = [
+    (mkRemovedOptionModule [ "url" ] ''
+      This option was removed. The URL of the endpoint serving JSON
+      must now be provided to the exporter by prometheus via the url
+      parameter `target'.
+
+      In prometheus a scrape URL would look like this:
+
+        http://some.json-exporter.host:7979/probe?target=https://example.com/some/json/endpoint
+
+      For more information, take a look at the official documentation
+      (https://github.com/prometheus-community/json_exporter) of the json_exporter.
+    '')
+     ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
index 1f02ae2072499..78fe120e4d932 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -10,64 +10,55 @@ let
       echo '${builtins.toJSON conf}' | ${pkgs.buildPackages.jq}/bin/jq '.' > $out
     '';
 
-  generateConfig = extraLabels: (map (path: {
-    name = "rspamd_${replaceStrings [ "." " " ] [ "_" "_" ] path}";
-    path = "$.${path}";
-    labels = extraLabels;
-  }) [
-    "actions.'add header'"
-    "actions.'no action'"
-    "actions.'rewrite subject'"
-    "actions.'soft reject'"
-    "actions.greylist"
-    "actions.reject"
-    "bytes_allocated"
-    "chunks_allocated"
-    "chunks_freed"
-    "chunks_oversized"
-    "connections"
-    "control_connections"
-    "ham_count"
-    "learned"
-    "pools_allocated"
-    "pools_freed"
-    "read_only"
-    "scanned"
-    "shared_chunks_allocated"
-    "spam_count"
-    "total_learns"
-  ]) ++ [{
-    name = "rspamd_statfiles";
-    type = "object";
-    path = "$.statfiles[*]";
-    labels = recursiveUpdate {
-      symbol = "$.symbol";
-      type = "$.type";
-    } extraLabels;
-    values = {
-      revision = "$.revision";
-      size = "$.size";
-      total = "$.total";
-      used = "$.used";
-      languages = "$.languages";
-      users = "$.users";
-    };
-  }];
+  generateConfig = extraLabels: {
+    metrics = (map (path: {
+      name = "rspamd_${replaceStrings [ "." " " ] [ "_" "_" ] path}";
+      path = "$.${path}";
+      labels = extraLabels;
+    }) [
+      "actions.'add header'"
+      "actions.'no action'"
+      "actions.'rewrite subject'"
+      "actions.'soft reject'"
+      "actions.greylist"
+      "actions.reject"
+      "bytes_allocated"
+      "chunks_allocated"
+      "chunks_freed"
+      "chunks_oversized"
+      "connections"
+      "control_connections"
+      "ham_count"
+      "learned"
+      "pools_allocated"
+      "pools_freed"
+      "read_only"
+      "scanned"
+      "shared_chunks_allocated"
+      "spam_count"
+      "total_learns"
+    ]) ++ [{
+      name = "rspamd_statfiles";
+      type = "object";
+      path = "$.statfiles[*]";
+      labels = recursiveUpdate {
+        symbol = "$.symbol";
+        type = "$.type";
+      } extraLabels;
+      values = {
+        revision = "$.revision";
+        size = "$.size";
+        total = "$.total";
+        used = "$.used";
+        languages = "$.languages";
+        users = "$.users";
+      };
+    }];
+  };
 in
 {
   port = 7980;
   extraOpts = {
-    listenAddress = {}; # not used
-
-    url = mkOption {
-      type = types.str;
-      description = ''
-        URL to the rspamd metrics endpoint.
-        Defaults to http://localhost:11334/stat when
-        <option>services.rspamd.enable</option> is true.
-      '';
-    };
-
     extraLabels = mkOption {
       type = types.attrsOf types.str;
       default = {
@@ -84,9 +75,25 @@ in
     };
   };
   serviceOpts.serviceConfig.ExecStart = ''
-    ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
-      --port ${toString cfg.port} \
-      ${cfg.url} ${prettyJSON (generateConfig cfg.extraLabels)} \
+    ${pkgs.prometheus-json-exporter}/bin/json_exporter \
+      --config.file ${prettyJSON (generateConfig cfg.extraLabels)} \
+      --web.listen-address "${cfg.listenAddress}:${toString cfg.port}" \
       ${concatStringsSep " \\\n  " cfg.extraFlags}
   '';
+
+  imports = [
+    (mkRemovedOptionModule [ "url" ] ''
+      This option was removed. The URL of the rspamd metrics endpoint
+      must now be provided to the exporter by prometheus via the url
+      parameter `target'.
+
+      In prometheus a scrape URL would look like this:
+
+        http://some.rspamd-exporter.host:7980/probe?target=http://some.rspamd.host:11334/stat
+
+      For more information, take a look at the official documentation
+      (https://github.com/prometheus-community/json_exporter) of the json_exporter.
+    '')
+     ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
new file mode 100644
index 0000000000000..0a7bb9c27be28
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.smokeping;
+  goDuration = types.mkOptionType {
+    name = "goDuration";
+    description = "Go duration (https://golang.org/pkg/time/#ParseDuration)";
+    check = x: types.str.check x && builtins.match "(-?[0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+" x != null;
+    inherit (types.str) merge;
+  };
+in
+{
+  port = 9374;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    pingInterval = mkOption {
+      type = goDuration;
+      default = "1s";
+      description = ''
+        Interval between pings.
+      '';
+    };
+    buckets = mkOption {
+      type = types.commas;
+      default = "5e-05,0.0001,0.0002,0.0004,0.0008,0.0016,0.0032,0.0064,0.0128,0.0256,0.0512,0.1024,0.2048,0.4096,0.8192,1.6384,3.2768,6.5536,13.1072,26.2144";
+      description = ''
+        List of buckets to use for the response duration histogram.
+      '';
+    };
+    hosts = mkOption {
+      type = with types; listOf str;
+      description = ''
+        List of endpoints to probe.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      AmbientCapabilities = [ "CAP_NET_RAW" ];
+      ExecStart = ''
+        ${pkgs.prometheus-smokeping-prober}/bin/smokeping_prober \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --buckets ${cfg.buckets} \
+          --ping.interval ${cfg.pingInterval} \
+          --privileged \
+          ${concatStringsSep " \\\n  " cfg.extraFlags} \
+          ${concatStringsSep " " cfg.hosts}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index d90a5923bc62e..4acdd1d69cc49 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/nixos/modules/services/networking/corerad.nix
@@ -4,13 +4,7 @@ with lib;
 
 let
   cfg = config.services.corerad;
-
-  writeTOML = name: x:
-    pkgs.runCommandNoCCLocal name {
-      passAsFile = ["config"];
-      config = builtins.toJSON x;
-      buildInputs = [ pkgs.go-toml ];
-    } "jsontoml < $configPath > $out";
+  settingsFormat = pkgs.formats.toml {};
 
 in {
   meta.maintainers = with maintainers; [ mdlayher ];
@@ -19,7 +13,7 @@ in {
     enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
 
     settings = mkOption {
-      type = types.uniq types.attrs;
+      type = settingsFormat.type;
       example = literalExample ''
         {
           interfaces = [
@@ -64,7 +58,7 @@ in {
 
   config = mkIf cfg.enable {
     # Prefer the config file over settings if both are set.
-    services.corerad.configFile = mkDefault (writeTOML "corerad.toml" cfg.settings);
+    services.corerad.configFile = mkDefault (settingsFormat.generate "corerad.toml" cfg.settings);
 
     systemd.services.corerad = {
       description = "CoreRAD IPv6 NDP RA daemon";
diff --git a/nixos/modules/services/networking/shellhub-agent.nix b/nixos/modules/services/networking/shellhub-agent.nix
new file mode 100644
index 0000000000000..4ce4b8250bc3c
--- /dev/null
+++ b/nixos/modules/services/networking/shellhub-agent.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.shellhub-agent;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.shellhub-agent = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the ShellHub Agent daemon, which allows
+          secure remote logins.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.shellhub-agent;
+        defaultText = "pkgs.shellhub-agent";
+        description = ''
+          Which ShellHub Agent package to use.
+        '';
+      };
+
+      tenantId = mkOption {
+        type = types.str;
+        example = "ba0a880c-2ada-11eb-a35e-17266ef329d6";
+        description = ''
+          The tenant ID to use when connecting to the ShellHub
+          Gateway.
+        '';
+      };
+
+      server = mkOption {
+        type = types.str;
+        default = "https://cloud.shellhub.io";
+        description = ''
+          Server address of ShellHub Gateway to connect.
+        '';
+      };
+
+      privateKey = mkOption {
+        type = types.path;
+        default = "/var/lib/shellhub-agent/private.key";
+        description = ''
+          Location where to store the ShellHub Agent private
+          key.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.shellhub-agent = {
+      description = "ShellHub Agent";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "local-fs.target" ];
+      wants = [ "network-online.target" ];
+      after = [
+        "local-fs.target"
+        "network.target"
+        "network-online.target"
+        "time-sync.target"
+      ];
+
+      environment.SERVER_ADDRESS = cfg.server;
+      environment.PRIVATE_KEY = cfg.privateKey;
+      environment.TENANT_ID = cfg.tenantId;
+
+      serviceConfig = {
+        # The service starts sessions for different users.
+        User = "root";
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/agent";
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/ttys/agetty.nix b/nixos/modules/services/ttys/getty.nix
index d07746be2377a..68ab81837772a 100644
--- a/nixos/modules/services/ttys/agetty.nix
+++ b/nixos/modules/services/ttys/getty.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  autologinArg = optionalString (config.services.mingetty.autologinUser != null) "--autologin ${config.services.mingetty.autologinUser}";
+  autologinArg = optionalString (config.services.getty.autologinUser != null) "--autologin ${config.services.getty.autologinUser}";
   gettyCmd = extraArgs: "@${pkgs.util-linux}/sbin/agetty agetty --login-program ${pkgs.shadow}/bin/login ${autologinArg} ${extraArgs}";
 
 in
@@ -13,9 +13,13 @@ in
 
   ###### interface
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "mingetty" ] [ "services" "getty" ])
+  ];
+
   options = {
 
-    services.mingetty = {
+    services.getty = {
 
       autologinUser = mkOption {
         type = types.nullOr types.str;
@@ -29,7 +33,7 @@ in
       greetingLine = mkOption {
         type = types.str;
         description = ''
-          Welcome line printed by mingetty.
+          Welcome line printed by agetty.
           The default shows current NixOS version label, machine type and tty.
         '';
       };
@@ -38,7 +42,7 @@ in
         type = types.lines;
         default = "";
         description = ''
-          Help line printed by mingetty below the welcome line.
+          Help line printed by agetty below the welcome line.
           Used by the installation CD to give some hints on
           how to proceed.
         '';
@@ -65,7 +69,7 @@ in
   config = {
     # Note: this is set here rather than up there so that changing
     # nixos.label would not rebuild manual pages
-    services.mingetty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>'';
+    services.getty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>'';
 
     systemd.services."getty@" =
       { serviceConfig.ExecStart = [
@@ -76,7 +80,7 @@ in
       };
 
     systemd.services."serial-getty@" =
-      let speeds = concatStringsSep "," (map toString config.services.mingetty.serialSpeed); in
+      let speeds = concatStringsSep "," (map toString config.services.getty.serialSpeed); in
       { serviceConfig.ExecStart = [
           "" # override upstream default with an empty ExecStart
           (gettyCmd "%I ${speeds} $TERM")
@@ -106,8 +110,8 @@ in
       { # Friendly greeting on the virtual consoles.
         source = pkgs.writeText "issue" ''
 
-          ${config.services.mingetty.greetingLine}
-          ${config.services.mingetty.helpLine}
+          ${config.services.getty.greetingLine}
+          ${config.services.getty.helpLine}
 
         '';
       };
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 0071951283478..e94861a90b5a8 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) literalExample mapAttrs optionalString;
+  inherit (lib) literalExample mapAttrs optionalString versionAtLeast;
 
   cfg = config.services.zabbixWeb;
   fpm = config.services.phpfpm.pools.zabbix;
@@ -28,6 +28,8 @@ let
     $ZBX_SERVER_PORT = '${toString cfg.server.port}';
     $ZBX_SERVER_NAME = ''';
     $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
+
+    ${cfg.extraConfig}
   '';
 
 in
@@ -143,6 +145,14 @@ in
         '';
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional configuration to be copied verbatim into <filename>zabbix.conf.php</filename>.
+        '';
+      };
+
     };
   };
 
@@ -150,6 +160,10 @@ in
 
   config = mkIf cfg.enable {
 
+    services.zabbixWeb.extraConfig = optionalString ((versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")) ''
+      $DB['DOUBLE_IEEE754'] = 'true';
+    '';
+
     systemd.tmpfiles.rules = [
       "d '${stateDir}' 0750 ${user} ${group} - -"
       "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index dc78728d66360..de3c7d693d47b 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -126,6 +126,13 @@ let
     </IfModule>
   '';
 
+  luaSetPaths = ''
+    <IfModule mod_lua.c>
+      LuaPackageCPath ${cfg.package.lua5}/lib/lua/${cfg.package.lua5.lua.luaversion}/?.so
+      LuaPackagePath  ${cfg.package.lua5}/share/lua/${cfg.package.lua5.lua.luaversion}/?.lua
+    </IfModule>
+  '';
+
   mkVHostConf = hostOpts:
     let
       adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
@@ -326,6 +333,8 @@ let
 
     ${sslConf}
 
+    ${if cfg.package.luaSupport then luaSetPaths else ""}
+
     # Fascist default - deny access to everything.
     <Directory />
         Options FollowSymLinks
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 62671e9d74849..7fcd61880ea88 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -27,6 +27,33 @@ let
   ) cfg.virtualHosts;
   enableIPv6 = config.networking.enableIPv6;
 
+  defaultFastcgiParams = {
+    SCRIPT_FILENAME   = "$document_root$fastcgi_script_name";
+    QUERY_STRING      = "$query_string";
+    REQUEST_METHOD    = "$request_method";
+    CONTENT_TYPE      = "$content_type";
+    CONTENT_LENGTH    = "$content_length";
+
+    SCRIPT_NAME       = "$fastcgi_script_name";
+    REQUEST_URI       = "$request_uri";
+    DOCUMENT_URI      = "$document_uri";
+    DOCUMENT_ROOT     = "$document_root";
+    SERVER_PROTOCOL   = "$server_protocol";
+    REQUEST_SCHEME    = "$scheme";
+    HTTPS             = "$https if_not_empty";
+
+    GATEWAY_INTERFACE = "CGI/1.1";
+    SERVER_SOFTWARE   = "nginx/$nginx_version";
+
+    REMOTE_ADDR       = "$remote_addr";
+    REMOTE_PORT       = "$remote_port";
+    SERVER_ADDR       = "$server_addr";
+    SERVER_PORT       = "$server_port";
+    SERVER_NAME       = "$server_name";
+
+    REDIRECT_STATUS   = "200";
+  };
+
   recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" ''
     proxy_set_header        Host $host;
     proxy_set_header        X-Real-IP $remote_addr;
@@ -283,6 +310,10 @@ let
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
       ''}
+      ${concatStringsSep "\n"
+        (mapAttrsToList (n: v: ''fastcgi_param ${n} "${v}";'')
+          (optionalAttrs (config.fastcgiParams != {})
+            (defaultFastcgiParams // config.fastcgiParams)))}
       ${optionalString (config.index != null) "index ${config.index};"}
       ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
       ${optionalString (config.root != null) "root ${config.root};"}
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index f2fc072557256..5a7f5188b6cfe 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -101,6 +101,16 @@ with lib;
       '';
     };
 
+    fastcgiParams = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      description = ''
+        FastCGI parameters to override.  Unlike in the Nginx
+        configuration file, overriding only some default parameters
+        won't unset the default values for other parameters.
+      '';
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index a9b0b8dc23fb7..5f1c099c283d8 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -8,8 +8,7 @@ let
   cfg = xcfg.desktopManager.plasma5;
 
   inherit (pkgs) kdeApplications kdeFrameworks plasma5;
-  libsForQt5 = pkgs.libsForQt514;
-  qt5 = pkgs.qt514;
+  inherit (pkgs) qt5 libsForQt5;
   inherit (pkgs) writeText;
 
   pulseaudio = config.hardware.pulseaudio;
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 23b4d8ffb1376..116994db1c140 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -1,9 +1,7 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-
 let
-
   xcfg = config.services.xserver;
   dmcfg = xcfg.displayManager;
   cfg = dmcfg.sddm;
@@ -11,87 +9,86 @@ let
 
   sddm = pkgs.libsForQt5.sddm;
 
-  xserverWrapper = pkgs.writeScript "xserver-wrapper" ''
-    #!/bin/sh
+  iniFmt = pkgs.formats.ini { };
+
+  xserverWrapper = pkgs.writeShellScript "xserver-wrapper" ''
     ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
     exec systemd-cat -t xserver-wrapper ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
   '';
 
-  Xsetup = pkgs.writeScript "Xsetup" ''
-    #!/bin/sh
+  Xsetup = pkgs.writeShellScript "Xsetup" ''
     ${cfg.setupScript}
     ${dmcfg.setupCommands}
   '';
 
-  Xstop = pkgs.writeScript "Xstop" ''
-    #!/bin/sh
+  Xstop = pkgs.writeShellScript "Xstop" ''
     ${cfg.stopScript}
   '';
 
-  cfgFile = pkgs.writeText "sddm.conf" ''
-    [General]
-    HaltCommand=/run/current-system/systemd/bin/systemctl poweroff
-    RebootCommand=/run/current-system/systemd/bin/systemctl reboot
-    ${optionalString cfg.autoNumlock ''
-    Numlock=on
-    ''}
-
-    [Theme]
-    Current=${cfg.theme}
-    ThemeDir=/run/current-system/sw/share/sddm/themes
-    FacesDir=/run/current-system/sw/share/sddm/faces
-
-    [Users]
-    MaximumUid=${toString config.ids.uids.nixbld}
-    HideUsers=${concatStringsSep "," dmcfg.hiddenUsers}
-    HideShells=/run/current-system/sw/bin/nologin
-
-    [X11]
-    MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)}
-    ServerPath=${xserverWrapper}
-    XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr
-    SessionCommand=${dmcfg.sessionData.wrapper}
-    SessionDir=${dmcfg.sessionData.desktops}/share/xsessions
-    XauthPath=${pkgs.xorg.xauth}/bin/xauth
-    DisplayCommand=${Xsetup}
-    DisplayStopCommand=${Xstop}
-    EnableHidpi=${boolToString cfg.enableHidpi}
-
-    [Wayland]
-    EnableHidpi=${boolToString cfg.enableHidpi}
-    SessionDir=${dmcfg.sessionData.desktops}/share/wayland-sessions
-
-    ${optionalString dmcfg.autoLogin.enable ''
-    [Autologin]
-    User=${dmcfg.autoLogin.user}
-    Session=${autoLoginSessionName}.desktop
-    Relogin=${boolToString cfg.autoLogin.relogin}
-    ''}
-
-    ${cfg.extraConfig}
-  '';
+  defaultConfig = {
+    General = {
+      HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff";
+      RebootCommand = "/run/current-system/systemd/bin/systemctl reboot";
+      Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none
+    };
+
+    Theme = {
+      Current = cfg.theme;
+      ThemeDir = "/run/current-system/sw/share/sddm/themes";
+      FacesDir = "/run/current-system/sw/share/sddm/faces";
+    };
+
+    Users = {
+      MaximumUid = config.ids.uids.nixbld;
+      HideUsers = concatStringsSep "," dmcfg.hiddenUsers;
+      HideShells = "/run/current-system/sw/bin/nologin";
+    };
 
-  autoLoginSessionName = dmcfg.sessionData.autologinSession;
+    X11 = {
+      MinimumVT = if xcfg.tty != null then xcfg.tty else 7;
+      ServerPath = toString xserverWrapper;
+      XephyrPath = "${pkgs.xorg.xorgserver.out}/bin/Xephyr";
+      SessionCommand = toString dmcfg.sessionData.wrapper;
+      SessionDir = "${dmcfg.sessionData.desktops}/share/xsessions";
+      XauthPath = "${pkgs.xorg.xauth}/bin/xauth";
+      DisplayCommand = toString Xsetup;
+      DisplayStopCommand = toString Xstop;
+      EnableHiDPI = cfg.enableHidpi;
+    };
+
+    Wayland = {
+      EnableHiDPI = cfg.enableHidpi;
+      SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+    };
+  } // lib.optionalAttrs dmcfg.autoLogin.enable {
+    Autologin = {
+      User = dmcfg.autoLogin.user;
+      Session = autoLoginSessionName;
+      Relogin = cfg.autoLogin.relogin;
+    };
+  };
+
+  cfgFile =
+    iniFmt.generate "sddm.conf" (lib.recursiveUpdate defaultConfig cfg.settings);
+
+  autoLoginSessionName =
+    "${dmcfg.sessionData.autologinSession}.desktop";
 
 in
 {
   imports = [
-    (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ]
+    (mkRemovedOptionModule
+      [ "services" "xserver" "displayManager" "sddm" "themes" ]
       "Set the option `services.xserver.displayManager.sddm.package' instead.")
-    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "enable" ] [
-      "services"
-      "xserver"
-      "displayManager"
-      "autoLogin"
-      "enable"
-    ])
-    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "sddm" "autoLogin" "user" ] [
-      "services"
-      "xserver"
-      "displayManager"
-      "autoLogin"
-      "user"
-    ])
+    (mkRenamedOptionModule
+      [ "services" "xserver" "displayManager" "sddm" "autoLogin" "enable" ]
+      [ "services" "xserver" "displayManager" "autoLogin" "enable" ])
+    (mkRenamedOptionModule
+      [ "services" "xserver" "displayManager" "sddm" "autoLogin" "user" ]
+      [ "services" "xserver" "displayManager" "autoLogin" "user" ])
+    (mkRemovedOptionModule
+      [ "services" "xserver" "displayManager" "sddm" "extraConfig" ]
+      "Set the option `services.xserver.displayManager.sddm.settings' instead.")
   ];
 
   options = {
@@ -110,22 +107,22 @@ in
         default = true;
         description = ''
           Whether to enable automatic HiDPI mode.
-          </para>
-          <para>
-          Versions up to 0.17 are broken so this only works from 0.18 onwards.
         '';
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
+      settings = mkOption {
+        type = iniFmt.type;
+        default = { };
         example = ''
-          [Autologin]
-          User=john
-          Session=plasma.desktop
+          {
+            Autologin = {
+              User = "john";
+              Session = "plasma.desktop";
+            };
+          }
         '';
         description = ''
-          Extra lines appended to the configuration of SDDM.
+          Extra settings merged in and overwritting defaults in sddm.conf.
         '';
       };
 
@@ -168,28 +165,38 @@ in
       };
 
       # Configuration for automatic login specific to SDDM
-      autoLogin.relogin = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          If true automatic login will kick in again on session exit (logout), otherwise it
-          will only log in automatically when the display-manager is started.
-        '';
+      autoLogin = {
+        relogin = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            If true automatic login will kick in again on session exit (logout), otherwise it
+            will only log in automatically when the display-manager is started.
+          '';
+        };
+
+        minimumUid = mkOption {
+          type = types.ints.u16;
+          default = 1000;
+          description = ''
+            Minimum user ID for auto-login user.
+          '';
+        };
       };
-
     };
-
   };
 
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = xcfg.enable;
+      {
+        assertion = xcfg.enable;
         message = ''
           SDDM requires services.xserver.enable to be true
         '';
       }
-      { assertion = dmcfg.autoLogin.enable -> autoLoginSessionName != null;
+      {
+        assertion = dmcfg.autoLogin.enable -> autoLoginSessionName != null;
         message = ''
           SDDM auto-login requires that services.xserver.displayManager.defaultSession is set.
         '';
@@ -230,7 +237,7 @@ in
 
       sddm-autologin.text = ''
         auth     requisite pam_nologin.so
-        auth     required  pam_succeed_if.so uid >= 1000 quiet
+        auth     required  pam_succeed_if.so uid >= ${toString cfg.autoLogin.minimumUid} quiet
         auth     required  pam_permit.so
 
         account  include   sddm
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index b9013ca1ff9f1..2bb4827be9d5d 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -5,25 +5,37 @@ let
   inherit (lib) mkOption mkIf optionals literalExample;
   cfg = config.services.xserver.windowManager.xmonad;
 
+  ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
+  packages = self: cfg.extraPackages self ++
+                   optionals cfg.enableContribAndExtras
+                   [ self.xmonad-contrib self.xmonad-extras ];
+
   xmonad-vanilla = pkgs.xmonad-with-packages.override {
-    ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
-    packages = self: cfg.extraPackages self ++
-                     optionals cfg.enableContribAndExtras
-                     [ self.xmonad-contrib self.xmonad-extras ];
+    inherit ghcWithPackages packages;
   };
 
-  xmonad-config = pkgs.writers.writeHaskellBin "xmonad" {
-    ghc = cfg.haskellPackages.ghc;
-    libraries = [ cfg.haskellPackages.xmonad ] ++
-                cfg.extraPackages cfg.haskellPackages ++
-                optionals cfg.enableContribAndExtras
-                (with cfg.haskellPackages; [ xmonad-contrib xmonad-extras ]);
-    inherit (cfg) ghcArgs;
-  } cfg.config;
+  xmonad-config =
+    let
+      xmonadAndPackages = self: [ self.xmonad ] ++ packages self;
+      xmonadEnv = ghcWithPackages xmonadAndPackages;
+      configured = pkgs.writers.writeHaskellBin "xmonad" {
+        ghc = cfg.haskellPackages.ghc;
+        libraries = xmonadAndPackages cfg.haskellPackages;
+        inherit (cfg) ghcArgs;
+      } cfg.config;
+    in
+      pkgs.runCommandLocal "xmonad" {
+        nativeBuildInputs = [ pkgs.makeWrapper ];
+      } ''
+        install -D ${xmonadEnv}/share/man/man1/xmonad.1.gz $out/share/man/man1/xmonad.1.gz
+        makeWrapper ${configured}/bin/xmonad $out/bin/xmonad \
+          --set NIX_GHC "${xmonadEnv}/bin/ghc" \
+          --set XMONAD_XMESSAGE "${pkgs.xorg.xmessage}/bin/xmessage"
+      '';
 
   xmonad = if (cfg.config != null) then xmonad-config else xmonad-vanilla;
 in {
-  meta.maintainers = with maintainers; [ lassulus xaverdh ];
+  meta.maintainers = with maintainers; [ lassulus xaverdh ivanbrennan ];
 
   options = {
     services.xserver.windowManager.xmonad = {
@@ -72,13 +84,13 @@ in {
           This setup is then analogous to other (non-NixOS) linux distributions.
 
           If you do set this option, you likely want to use "launch" as your
-          entry point for xmonad (as in the example), to avoid xmonads
+          entry point for xmonad (as in the example), to avoid xmonad's
           recompilation logic on startup. Doing so will render the default
           "mod+q" restart key binding dysfunctional though, because that attempts
           to call your binary with the "--restart" command line option, unless
           you implement that yourself. You way mant to bind "mod+q" to
           <literal>(restart "xmonad" True)</literal> instead, which will just restart
-          xmonad from PATH. This allows e.g. switching to the new xmonad binary,
+          xmonad from PATH. This allows e.g. switching to the new xmonad binary
           after rebuilding your system with nixos-rebuild.
 
           If you actually want to run xmonad with a config specified here, but
@@ -91,6 +103,7 @@ in {
         example = ''
           import XMonad
           import XMonad.Util.EZConfig (additionalKeys)
+          import Control.Monad (when)
           import Text.Printf (printf)
           import System.Posix.Process (executeFile)
           import System.Info (arch,os)
@@ -99,16 +112,21 @@ in {
 
           compiledConfig = printf "xmonad-%s-%s" arch os
 
-          compileRestart = whenX (recompile True) . catchIO $ do
-              dir  <- getXMonadDataDir
-              args <- getArgs
-              executeFile (dir </> compiledConfig) False args Nothing
+          compileRestart resume =
+            whenX (recompile True) $
+              when resume writeStateToFile
+                *> catchIO
+                  ( do
+                      dir <- getXMonadDataDir
+                      args <- getArgs
+                      executeFile (dir </> compiledConfig) False args Nothing
+                  )
 
           main = launch defaultConfig
               { modMask = mod4Mask -- Use Super instead of Alt
               , terminal = "urxvt" }
               `additionalKeys`
-              [ ( (mod4Mask,xK_r), compileRestart )
+              [ ( (mod4Mask,xK_r), compileRestart True)
               , ( (mod4Mask,xK_q), restart "xmonad" True ) ]
         '';
       };
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 5fda24adb9782..ac41ba5f93a4b 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -49,8 +49,8 @@ in
     }
 
     (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
-      # the cryptographic modules are required only for decryption attempts
-      boot.initrd.availableKernelModules = [ "bcachefs" "chacha20" "poly1305" ];
+      # chacha20 and poly1305 are required only for decryption attempts
+      boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
 
       boot.initrd.extraUtilsCommands = ''
         copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
diff --git a/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix
index 8c12e0e49bf5b..c5470b7af09b0 100644
--- a/nixos/modules/virtualisation/amazon-init.nix
+++ b/nixos/modules/virtualisation/amazon-init.nix
@@ -7,7 +7,7 @@ let
     echo "attempting to fetch configuration from EC2 user data..."
 
     export HOME=/root
-    export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused config.system.build.nixos-rebuild]}:$PATH
+    export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused pkgs.xz config.system.build.nixos-rebuild]}:$PATH
     export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
 
     userData=/etc/ec2-metadata/user-data
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index d493648401872..e47bd59dc016b 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -11,7 +11,7 @@ with lib;
   users.users.root.initialHashedPassword = mkOverride 150 "";
 
   # Some more help text.
-  services.mingetty.helpLine =
+  services.getty.helpLine =
     ''
 
       Log in as "root" with an empty password.
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 26398afb3cf51..757d73421b8f2 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -56,10 +56,10 @@ let
             ip -6 route add $HOST_ADDRESS6 dev eth0
             ip -6 route add default via $HOST_ADDRESS6
           fi
-
-          ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
         fi
 
+        ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
+
         # Start the regular stage 1 script.
         exec "$1"
       ''
@@ -223,8 +223,8 @@ let
             ${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"}
             ${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"}
           fi
-          ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
         fi
+        ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
       ''
   );
 
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index a46dd65eb4910..ee9fe62187d33 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -176,10 +176,10 @@ let
           description = ''
             Define which other containers this one depends on. They will be added to both After and Requires for the unit.
 
-            Use the same name as the attribute under <literal>virtualisation.oci-containers</literal>.
+            Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
           '';
           example = literalExample ''
-            virtualisation.oci-containers = {
+            virtualisation.oci-containers.containers = {
               node1 = {};
               node2 = {
                 dependsOn = [ "node1" ];
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 659e538b31d02..c491b559213c9 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -158,6 +158,7 @@ in
   home-assistant = handleTest ./home-assistant.nix {};
   hostname = handleTest ./hostname.nix {};
   hound = handleTest ./hound.nix {};
+  hub = handleTest ./git/hub.nix {};
   hydra = handleTest ./hydra {};
   i3wm = handleTest ./i3wm.nix {};
   icingaweb2 = handleTest ./icingaweb2.nix {};
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 8402ba68b7209..369ef94f9fadc 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -247,5 +247,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             ).strip()
             == "${if pkgs.system == "aarch64-linux" then "amd64" else "arm64"}"
         )
+
+    with subtest("buildLayeredImage doesn't dereference /nix/store symlink layers"):
+        docker.succeed(
+            "docker load --input='${examples.layeredStoreSymlink}'",
+            "docker run --rm ${examples.layeredStoreSymlink.imageName} bash -c 'test -L ${examples.layeredStoreSymlink.passthru.symlink}'",
+            "docker rmi ${examples.layeredStoreSymlink.imageName}",
+        )
   '';
 })
diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix
index bcbe234fd8052..1129e3b45d9d6 100644
--- a/nixos/tests/dovecot.nix
+++ b/nixos/tests/dovecot.nix
@@ -4,8 +4,11 @@ import ./make-test-python.nix {
   machine = { pkgs, ... }: {
     imports = [ common/user-account.nix ];
     services.postfix.enable = true;
-    services.dovecot2.enable = true;
-    services.dovecot2.protocols = [ "imap" "pop3" ];
+    services.dovecot2 = {
+      enable = true;
+      protocols = [ "imap" "pop3" ];
+      modules = [ pkgs.dovecot_pigeonhole ];
+    };
     environment.systemPackages = let
       sendTestMail = pkgs.writeScriptBin "send-testmail" ''
         #!${pkgs.runtimeShell}
diff --git a/nixos/tests/git/hub.nix b/nixos/tests/git/hub.nix
new file mode 100644
index 0000000000000..e2359e887efbd
--- /dev/null
+++ b/nixos/tests/git/hub.nix
@@ -0,0 +1,17 @@
+import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "hub";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes.hub = { pkgs, ... }:
+    {
+      environment.systemPackages = [ pkgs.gitAndTools.hub ];
+    };
+
+  testScript =
+    ''
+      assert "git version ${pkgs.git.version}\nhub version ${pkgs.gitAndTools.hub.version}\n" in hub.succeed("hub version")
+      assert "These GitHub commands are provided by hub" in hub.succeed("hub help")
+    '';
+})
diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana.nix
index 4b453ece7f1ef..4ba091b893f42 100644
--- a/nixos/tests/grafana.nix
+++ b/nixos/tests/grafana.nix
@@ -17,6 +17,10 @@ let
   };
 
   extraNodeConfs = {
+    declarativePlugins = {
+      services.grafana.declarativePlugins = [ pkgs.grafanaPlugins.grafana-clock-panel ];
+    };
+
     postgresql = {
       services.grafana.database = {
         host = "127.0.0.1:5432";
@@ -52,7 +56,7 @@ let
     nameValuePair dbName (mkMerge [
     baseGrafanaConf
     (extraNodeConfs.${dbName} or {})
-  ])) [ "sqlite" "postgresql" "mysql" ]);
+  ])) [ "sqlite" "declarativePlugins" "postgresql" "mysql" ]);
 
 in {
   name = "grafana";
@@ -66,6 +70,14 @@ in {
   testScript = ''
     start_all()
 
+    with subtest("Declarative plugins installed"):
+        declarativePlugins.wait_for_unit("grafana.service")
+        declarativePlugins.wait_for_open_port(3000)
+        declarativePlugins.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/plugins | grep -q grafana-clock-panel"
+        )
+        declarativePlugins.shutdown()
+
     with subtest("Successful API query as admin user with sqlite db"):
         sqlite.wait_for_unit("grafana.service")
         sqlite.wait_for_open_port(3000)
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index d36c1a91be432..ce11e1f942ab1 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -50,7 +50,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
       with subtest("Virtual console logout"):
           machine.send_chars("exit\n")
           machine.wait_until_fails("pgrep -u alice bash")
-          machine.screenshot("mingetty")
+          machine.screenshot("getty")
 
       with subtest("Check whether ctrl-alt-delete works"):
           machine.send_key("ctrl-alt-delete")
diff --git a/nixos/tests/loki.nix b/nixos/tests/loki.nix
index bede775b7d3c3..0c6dff3fdf137 100644
--- a/nixos/tests/loki.nix
+++ b/nixos/tests/loki.nix
@@ -46,7 +46,9 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     machine.wait_for_open_port(9080)
     machine.succeed("echo 'Loki Ingestion Test' > /var/log/testlog")
     # should not have access to journal unless specified
-    machine.fail("systemctl show --property=SupplementaryGroups promtail | grep -q systemd-journal")
+    machine.fail(
+        "systemctl show --property=SupplementaryGroups promtail | grep -q systemd-journal"
+    )
     machine.wait_until_succeeds(
         "${pkgs.grafana-loki}/bin/logcli --addr='http://localhost:3100' query --no-labels '{job=\"varlogs\",filename=\"/var/log/testlog\"}' | grep -q 'Loki Ingestion Test'"
     )
diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix
index 432b46234f9c1..0e8b3bfd6c34f 100644
--- a/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixos/tests/postgresql-wal-receiver.nix
@@ -1,11 +1,19 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
 let
+  lib = pkgs.lib;
+
   # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`.
   makePostgresqlWalReceiverTest = postgresqlPackage:
   {
     name = postgresqlPackage;
     value =
-      import ./make-test-python.nix ({ pkgs, lib, ... }: let
-
+      let
         pkg = pkgs."${postgresqlPackage}";
         postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}";
         replicationUser = "wal_receiver_user";
@@ -19,7 +27,7 @@ let
             then pkgs.writeTextDir "recovery.signal" ""
             else pkgs.writeTextDir "recovery.conf" "restore_command = 'cp ${walBackupDir}/%f %p'";
 
-      in {
+      in makeTest {
         name = "postgresql-wal-receiver-${postgresqlPackage}";
         meta.maintainers = with lib.maintainers; [ pacien ];
 
@@ -104,7 +112,7 @@ let
               "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
           )
         '';
-      });
+      };
     };
 
 # Maps the generic function over all attributes of PostgreSQL packages
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index ffa7f420c093d..8fcb0a7aa2c34 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -96,6 +96,31 @@ let
       '';
     };
 
+    bird = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.bird2.enable = true;
+        services.bird2.config = ''
+          protocol kernel MyObviousTestString {
+            ipv4 {
+              import all;
+              export none;
+            };
+          }
+
+          protocol device {
+          }
+        '';
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-bird-exporter.service")
+        wait_for_open_port(9324)
+        succeed("curl -sSf http://localhost:9324/metrics | grep -q 'MyObviousTestString'")
+      '';
+    };
+
     blackbox = {
       exporterConfig = {
         enable = true;
@@ -197,10 +222,11 @@ let
       exporterConfig = {
         enable = true;
         url = "http://localhost";
-        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON [{
-          name = "json_test_metric";
-          path = "$.test";
-        }]);
+        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON {
+          metrics = [
+            { name = "json_test_metric"; path = "$.test"; }
+          ];
+        });
       };
       metricProvider = {
         systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
@@ -216,7 +242,9 @@ let
         wait_for_open_port(80)
         wait_for_unit("prometheus-json-exporter.service")
         wait_for_open_port(7979)
-        succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'")
+        succeed(
+            "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep -q 'json_test_metric 1'"
+        )
       '';
     };
 
@@ -634,7 +662,7 @@ let
         wait_for_open_port(11334)
         wait_for_open_port(7980)
         wait_until_succeeds(
-            "curl -sSf localhost:7980/metrics | grep -q 'rspamd_scanned{host=\"rspamd\"} 0'"
+            "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep -q 'rspamd_scanned{host=\"rspamd\"} 0'"
         )
       '';
     };
@@ -670,6 +698,27 @@ let
       '';
     };
 
+    smokeping = {
+      exporterConfig = {
+        enable = true;
+        hosts = ["127.0.0.1"];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-smokeping-exporter.service")
+        wait_for_open_port(9374)
+        wait_until_succeeds(
+            "curl -sSf localhost:9374/metrics | grep '{}' | grep -qv ' 0$'".format(
+                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1"} '
+            )
+        )
+        wait_until_succeeds(
+            "curl -sSf localhost:9374/metrics | grep -q '{}'".format(
+                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1"}'
+            )
+        )
+      '';
+    };
+
     snmp = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index 0cc1d6811400c..8f8cdef7ef9d7 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -2,6 +2,7 @@ let
   password1 = "foobar";
   password2 = "helloworld";
   password3 = "bazqux";
+  password4 = "asdf123";
 in import ./make-test-python.nix ({ pkgs, ... }: {
   name = "shadow";
   meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ nequissimus ]; };
@@ -19,6 +20,10 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         password = password2;
         shell = pkgs.shadow;
       };
+      users.ash = {
+        password = password4;
+        shell = pkgs.bash;
+      };
     };
   };
 
@@ -41,6 +46,15 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_for_file("/tmp/1")
         assert "emma" in shadow.succeed("cat /tmp/1")
 
+    with subtest("Switch user"):
+        shadow.send_chars("su - ash\n")
+        shadow.sleep(2)
+        shadow.send_chars("${password4}\n")
+        shadow.sleep(2)
+        shadow.send_chars("whoami > /tmp/3\n")
+        shadow.wait_for_file("/tmp/3")
+        assert "ash" in shadow.succeed("cat /tmp/3")
+
     with subtest("Change password"):
         shadow.send_key("alt-f3")
         shadow.wait_until_succeeds(f"[ $(fgconsole) = 3 ]")