summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/acme.nix62
-rw-r--r--nixos/tests/agda.nix41
-rw-r--r--nixos/tests/all-tests.nix45
-rw-r--r--nixos/tests/bazarr.nix26
-rw-r--r--nixos/tests/bcachefs.nix2
-rw-r--r--nixos/tests/bitcoind.nix46
-rw-r--r--nixos/tests/bittorrent.nix24
-rw-r--r--nixos/tests/blockbook-frontend.nix28
-rw-r--r--nixos/tests/borgbackup.nix2
-rw-r--r--nixos/tests/ceph-multi-node.nix6
-rw-r--r--nixos/tests/ceph-single-node.nix6
-rw-r--r--nixos/tests/common/auto.nix4
-rw-r--r--nixos/tests/consul.nix136
-rw-r--r--nixos/tests/containers-portforward.nix2
-rw-r--r--nixos/tests/convos.nix30
-rw-r--r--nixos/tests/corerad.nix37
-rw-r--r--nixos/tests/dnscrypt-wrapper/default.nix71
-rw-r--r--nixos/tests/dnscrypt-wrapper/public.key1
-rw-r--r--nixos/tests/dnscrypt-wrapper/secret.key1
-rw-r--r--nixos/tests/docker-preloader.nix4
-rw-r--r--nixos/tests/docker-tools.nix89
-rw-r--r--nixos/tests/docker.nix4
-rw-r--r--nixos/tests/dokuwiki.nix24
-rw-r--r--nixos/tests/engelsystem.nix41
-rw-r--r--nixos/tests/enlightenment.nix16
-rw-r--r--nixos/tests/ergo.nix18
-rw-r--r--nixos/tests/firejail.nix82
-rw-r--r--nixos/tests/gnome3-xorg.nix6
-rw-r--r--nixos/tests/gnome3.nix6
-rw-r--r--nixos/tests/go-neb.nix44
-rw-r--r--nixos/tests/graphite.nix11
-rw-r--r--nixos/tests/grub.nix60
-rw-r--r--nixos/tests/haproxy.nix8
-rw-r--r--nixos/tests/home-assistant.nix134
-rw-r--r--nixos/tests/hostname.nix66
-rw-r--r--nixos/tests/hydra/common.nix1
-rw-r--r--nixos/tests/hydra/db-migration.nix6
-rw-r--r--nixos/tests/ihatemoney.nix13
-rw-r--r--nixos/tests/initrd-network-openvpn/default.nix145
-rw-r--r--nixos/tests/initrd-network-openvpn/initrd.ovpn29
-rw-r--r--nixos/tests/initrd-network-openvpn/shared.key21
-rw-r--r--nixos/tests/installed-tests/default.nix6
-rw-r--r--nixos/tests/installed-tests/flatpak.nix9
-rw-r--r--nixos/tests/installed-tests/ibus.nix14
-rw-r--r--nixos/tests/installed-tests/ostree.nix11
-rw-r--r--nixos/tests/installer.nix47
-rw-r--r--nixos/tests/ipfs.nix18
-rw-r--r--nixos/tests/jitsi-meet.nix55
-rw-r--r--nixos/tests/kubernetes/base.nix9
-rw-r--r--nixos/tests/kubernetes/dns.nix74
-rw-r--r--nixos/tests/kubernetes/rbac.nix86
-rw-r--r--nixos/tests/leaps.nix2
-rw-r--r--nixos/tests/lorri/default.nix2
-rw-r--r--nixos/tests/lxd-nftables.nix50
-rw-r--r--nixos/tests/lxd.nix135
-rw-r--r--nixos/tests/mathics.nix20
-rw-r--r--nixos/tests/matrix-synapse.nix4
-rw-r--r--nixos/tests/mesos.nix92
-rw-r--r--nixos/tests/mesos_test.py72
-rw-r--r--nixos/tests/misc.nix32
-rw-r--r--nixos/tests/molly-brown.nix71
-rw-r--r--nixos/tests/mongodb.nix6
-rw-r--r--nixos/tests/mysql/mariadb-galera-mariabackup.nix32
-rw-r--r--nixos/tests/mysql/mariadb-galera-rsync.nix32
-rw-r--r--nixos/tests/mysql/mysql-replication.nix12
-rw-r--r--nixos/tests/mysql/mysql.nix114
-rw-r--r--nixos/tests/ncdns.nix77
-rw-r--r--nixos/tests/networking.nix21
-rw-r--r--nixos/tests/nextcloud/basic.nix27
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix1
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix1
-rw-r--r--nixos/tests/nfs/kerberos.nix4
-rw-r--r--nixos/tests/nginx-pubhtml.nix1
-rw-r--r--nixos/tests/nginx-sandbox.nix66
-rw-r--r--nixos/tests/nginx-variants.nix33
-rw-r--r--nixos/tests/pinnwand.nix86
-rw-r--r--nixos/tests/plasma5.nix2
-rw-r--r--nixos/tests/podman.nix33
-rw-r--r--nixos/tests/postfix-raise-smtpd-tls-security-level.nix44
-rw-r--r--nixos/tests/postfix.nix76
-rw-r--r--nixos/tests/postgresql-wal-receiver.nix4
-rw-r--r--nixos/tests/privacyidea.nix36
-rw-r--r--nixos/tests/prometheus-exporters.nix117
-rw-r--r--nixos/tests/pt2-clone.nix35
-rw-r--r--nixos/tests/qboot.nix13
-rw-r--r--nixos/tests/radicale.nix28
-rw-r--r--nixos/tests/restic.nix55
-rw-r--r--nixos/tests/sddm.nix4
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix29
-rw-r--r--nixos/tests/slurm.nix93
-rw-r--r--nixos/tests/snapcast.nix58
-rw-r--r--nixos/tests/sogo.nix58
-rw-r--r--nixos/tests/sslh.nix83
-rw-r--r--nixos/tests/sudo.nix2
-rw-r--r--nixos/tests/syncthing-init.nix3
-rw-r--r--nixos/tests/syncthing.nix65
-rw-r--r--nixos/tests/systemd-binfmt.nix24
-rw-r--r--nixos/tests/systemd-boot.nix89
-rw-r--r--nixos/tests/systemd-networkd-vrf.nix24
-rw-r--r--nixos/tests/systemd.nix27
-rw-r--r--nixos/tests/taskserver.nix310
-rw-r--r--nixos/tests/teeworlds.nix55
-rw-r--r--nixos/tests/tiddlywiki.nix2
-rw-r--r--nixos/tests/transmission.nix2
-rw-r--r--nixos/tests/trezord.nix5
-rw-r--r--nixos/tests/trickster.nix6
-rw-r--r--nixos/tests/virtualbox.nix1
-rw-r--r--nixos/tests/wasabibackend.nix38
-rw-r--r--nixos/tests/web-servers/unit-php.nix14
-rw-r--r--nixos/tests/wireguard/basic.nix74
-rw-r--r--nixos/tests/wireguard/default.nix96
-rw-r--r--nixos/tests/wireguard/generated.nix5
-rw-r--r--nixos/tests/wireguard/namespaces.nix8
-rw-r--r--nixos/tests/wireguard/wg-quick.nix4
-rw-r--r--nixos/tests/xandikos.nix6
-rw-r--r--nixos/tests/xfce.nix4
-rw-r--r--nixos/tests/yggdrasil.nix43
-rw-r--r--nixos/tests/zfs.nix23
-rw-r--r--nixos/tests/zigbee2mqtt.nix19
-rw-r--r--nixos/tests/zoneminder.nix23
120 files changed, 3547 insertions, 913 deletions
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index fc41dc1eb5f..a8188473721 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -48,10 +48,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
       security.acme.certs."standalone.test" = {
         webroot = "/var/lib/acme/acme-challenges";
       };
-      systemd.targets."acme-finished-standalone.test" = {};
-      systemd.services."acme-standalone.test" = {
-        wants = [ "acme-finished-standalone.test.target" ];
-        before = [ "acme-finished-standalone.test.target" ];
+      systemd.targets."acme-finished-standalone.test" = {
+        after = [ "acme-standalone.test.service" ];
+        wantedBy = [ "acme-standalone.test.service" ];
       };
       services.nginx.enable = true;
       services.nginx.virtualHosts."standalone.test" = {
@@ -68,11 +67,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
 
       # A target remains active. Use this to probe the fact that
       # a service fired eventhough it is not RemainAfterExit
-      systemd.targets."acme-finished-a.example.test" = {};
-      systemd.services."acme-a.example.test" = {
-        wants = [ "acme-finished-a.example.test.target" ];
-        before = [ "acme-finished-a.example.test.target" ];
-        after = [ "nginx.service" ];
+      systemd.targets."acme-finished-a.example.test" = {
+        after = [ "acme-a.example.test.service" ];
+        wantedBy = [ "acme-a.example.test.service" ];
       };
 
       services.nginx.enable = true;
@@ -89,11 +86,9 @@ in import ./make-test-python.nix ({ lib, ... }: {
       security.acme.server = "https://acme.test/dir";
 
       specialisation.second-cert.configuration = {pkgs, ...}: {
-        systemd.targets."acme-finished-b.example.test" = {};
-        systemd.services."acme-b.example.test" = {
-          wants = [ "acme-finished-b.example.test.target" ];
-          before = [ "acme-finished-b.example.test.target" ];
-          after = [ "nginx.service" ];
+        systemd.targets."acme-finished-b.example.test" = {
+          after = [ "acme-b.example.test.service" ];
+          wantedBy = [ "acme-b.example.test.service" ];
         };
         services.nginx.virtualHosts."b.example.test" = {
           enableACME = true;
@@ -104,6 +99,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
           '';
         };
       };
+
       specialisation.dns-01.configuration = {pkgs, config, nodes, lib, ...}: {
         security.acme.certs."example.test" = {
           domain = "*.example.test";
@@ -115,10 +111,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
           user = config.services.nginx.user;
           group = config.services.nginx.group;
         };
-        systemd.targets."acme-finished-example.test" = {};
+        systemd.targets."acme-finished-example.test" = {
+          after = [ "acme-example.test.service" ];
+          wantedBy = [ "acme-example.test.service" ];
+        };
         systemd.services."acme-example.test" = {
-          wants = [ "acme-finished-example.test.target" ];
-          before = [ "acme-finished-example.test.target" "nginx.service" ];
+          before = [ "nginx.service" ];
           wantedBy = [ "nginx.service" ];
         };
         services.nginx.virtualHosts."c.example.test" = {
@@ -132,6 +130,26 @@ in import ./make-test-python.nix ({ lib, ... }: {
           '';
         };
       };
+
+      # When nginx depends on a service that is slow to start up, requesting used to fail
+      # certificates fail.  Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
+      specialisation.slow-startup.configuration = { pkgs, config, nodes, lib, ...}: {
+        systemd.services.my-slow-service = {
+          wantedBy = [ "multi-user.target" "nginx.service" ];
+          before = [ "nginx.service" ];
+          preStart = "sleep 5";
+          script = "${pkgs.python3}/bin/python -m http.server";
+        };
+        systemd.targets."acme-finished-d.example.com" = {
+          after = [ "acme-d.example.com.service" ];
+          wantedBy = [ "acme-d.example.com.service" ];
+        };
+        services.nginx.virtualHosts."d.example.com" = {
+          forceSSL = true;
+          enableACME = true;
+          locations."/".proxyPass = "http://localhost:8000";
+        };
+      };
     };
 
     client = {nodes, lib, ...}: {
@@ -207,5 +225,15 @@ in import ./make-test-python.nix ({ lib, ... }: {
           client.succeed(
               "curl --cacert /tmp/ca.crt https://c.example.test/ | grep -qF 'hello world'"
           )
+
+      with subtest("Can request certificate of nginx when startup is delayed"):
+          webserver.succeed(
+              "${switchToNewServer}"
+          )
+          webserver.succeed(
+              "/run/current-system/specialisation/slow-startup/bin/switch-to-configuration test"
+          )
+          webserver.wait_for_unit("acme-finished-d.example.com.target")
+          client.succeed("curl --cacert /tmp/ca.crt https://d.example.com/")
     '';
 })
diff --git a/nixos/tests/agda.nix b/nixos/tests/agda.nix
new file mode 100644
index 00000000000..e158999e57d
--- /dev/null
+++ b/nixos/tests/agda.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  hello-world = pkgs.writeText "hello-world" ''
+    open import IO
+
+    main = run(putStrLn "Hello World!")
+  '';
+in
+{
+  name = "agda";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ alexarice turion ];
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = [
+      (pkgs.agda.withPackages {
+        pkgs = p: [ p.standard-library ];
+      })
+    ];
+    virtualisation.memorySize = 2000; # Agda uses a lot of memory
+  };
+
+  testScript = ''
+    # Minimal script that typechecks
+    machine.succeed("touch TestEmpty.agda")
+    machine.succeed("agda TestEmpty.agda")
+
+    # Minimal script that actually uses the standard library
+    machine.succeed('echo "import IO" > TestIO.agda')
+    machine.succeed("agda -l standard-library -i . TestIO.agda")
+
+    # # Hello world
+    machine.succeed(
+        "cp ${hello-world} HelloWorld.agda"
+    )
+    machine.succeed("agda -l standard-library -i . -c HelloWorld.agda")
+  '';
+}
+)
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 5a0c9d1afae..a4a62d85a59 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -23,14 +23,18 @@ in
 {
   _3proxy = handleTest ./3proxy.nix {};
   acme = handleTest ./acme.nix {};
+  agda = handleTest ./agda.nix {};
   atd = handleTest ./atd.nix {};
   avahi = handleTest ./avahi.nix {};
   babeld = handleTest ./babeld.nix {};
+  bazarr = handleTest ./bazarr.nix {};
   bcachefs = handleTestOn ["x86_64-linux"] ./bcachefs.nix {}; # linux-4.18.2018.10.12 is unsupported on aarch64
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
   bind = handleTest ./bind.nix {};
+  bitcoind = handleTest ./bitcoind.nix {};
   bittorrent = handleTest ./bittorrent.nix {};
+  blockbook-frontend = handleTest ./blockbook-frontend.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
   boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64
   boot-stage1 = handleTest ./boot-stage1.nix {};
@@ -63,11 +67,13 @@ in
   containers-portforward = handleTest ./containers-portforward.nix {};
   containers-restart_networking = handleTest ./containers-restart_networking.nix {};
   containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  convos = handleTest ./convos.nix {};
   corerad = handleTest ./corerad.nix {};
   couchdb = handleTest ./couchdb.nix {};
   deluge = handleTest ./deluge.nix {};
   dhparams = handleTest ./dhparams.nix {};
   dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
+  dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
   doas = handleTest ./doas.nix {};
   docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
   oci-containers = handleTestOn ["x86_64-linux"] ./oci-containers.nix {};
@@ -85,14 +91,17 @@ in
   ecryptfs = handleTest ./ecryptfs.nix {};
   ejabberd = handleTest ./xmpp/ejabberd.nix {};
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
+  engelsystem = handleTest ./engelsystem.nix {};
   enlightenment = handleTest ./enlightenment.nix {};
   env = handleTest ./env.nix {};
+  ergo = handleTest ./ergo.nix {};
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix {};
   firefox-esr = handleTest ./firefox.nix { esr = true; };
+  firejail = handleTest ./firejail.nix {};
   firewall = handleTest ./firewall.nix {};
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
@@ -114,10 +123,12 @@ in
   installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
+  go-neb = handleTest ./go-neb.nix {};
   google-oslogin = handleTest ./google-oslogin {};
   grafana = handleTest ./grafana.nix {};
   graphite = handleTest ./graphite.nix {};
   graylog = handleTest ./graylog.nix {};
+  grub = handleTest ./grub.nix {};
   gvisor = handleTest ./gvisor.nix {};
   hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
   hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
@@ -131,6 +142,7 @@ in
   hitch = handleTest ./hitch {};
   hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
   home-assistant = handleTest ./home-assistant.nix {};
+  hostname = handleTest ./hostname.nix {};
   hound = handleTest ./hound.nix {};
   hydra = handleTest ./hydra {};
   hydra-db-migration = handleTest ./hydra/db-migration.nix {};
@@ -141,6 +153,7 @@ in
   incron = handleTest ./incron.nix {};
   influxdb = handleTest ./influxdb.nix {};
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   installer = handleTest ./installer.nix {};
   iodine = handleTest ./iodine.nix {};
@@ -150,6 +163,7 @@ in
   jellyfin = handleTest ./jellyfin.nix {};
   jenkins = handleTest ./jenkins.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
+  jitsi-meet = handleTest ./jitsi-meet.nix {};
   k3s = handleTest ./k3s.nix {};
   kafka = handleTest ./kafka.nix {};
   keepalived = handleTest ./keepalived.nix {};
@@ -172,6 +186,8 @@ in
   limesurvey = handleTest ./limesurvey.nix {};
   login = handleTest ./login.nix {};
   loki = handleTest ./loki.nix {};
+  lxd = handleTest ./lxd.nix {};
+  lxd-nftables = handleTest ./lxd-nftables.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
   magnetico = handleTest ./magnetico.nix {};
@@ -179,12 +195,10 @@ in
   mailcatcher = handleTest ./mailcatcher.nix {};
   mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {};
   mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {};
-  mathics = handleTest ./mathics.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   memcached = handleTest ./memcached.nix {};
-  mesos = handleTest ./mesos.nix {};
   metabase = handleTest ./metabase.nix {};
   miniflux = handleTest ./miniflux.nix {};
   minio = handleTest ./minio.nix {};
@@ -208,6 +222,7 @@ in
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  ncdns = handleTest ./ncdns.nix {};
   ndppd = handleTest ./ndppd.nix {};
   neo4j = handleTest ./neo4j.nix {};
   specialisation = handleTest ./specialisation.nix {};
@@ -225,7 +240,9 @@ in
   nginx = handleTest ./nginx.nix {};
   nginx-etag = handleTest ./nginx-etag.nix {};
   nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
+  nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
   nginx-sso = handleTest ./nginx-sso.nix {};
+  nginx-variants = handleTest ./nginx-variants.nix {};
   nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
   nixos-generate-config = handleTest ./nixos-generate-config.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
@@ -250,9 +267,12 @@ in
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
   php = handleTest ./php {};
+  pinnwand = handleTest ./pinnwand.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   plotinus = handleTest ./plotinus.nix {};
-  podman = handleTest ./podman.nix {};
+  podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
+  postfix = handleTest ./postfix.nix {};
+  postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
   postgis = handleTest ./postgis.nix {};
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
@@ -260,11 +280,14 @@ in
   pppd = handleTest ./pppd.nix {};
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
   printing = handleTest ./printing.nix {};
+  privacyidea = handleTest ./privacyidea.nix {};
   prometheus = handleTest ./prometheus.nix {};
   prometheus-exporters = handleTest ./prometheus-exporters.nix {};
   prosody = handleTest ./xmpp/prosody.nix {};
   prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {};
   proxy = handleTest ./proxy.nix {};
+  pt2-clone = handleTest ./pt2-clone.nix {};
+  qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   quagga = handleTest ./quagga.nix {};
   quorum = handleTest ./quorum.nix {};
   rabbitmq = handleTest ./rabbitmq.nix {};
@@ -283,25 +306,31 @@ in
   sanoid = handleTest ./sanoid.nix {};
   sddm = handleTest ./sddm.nix {};
   service-runner = handleTest ./service-runner.nix {};
+  shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
   shiori = handleTest ./shiori.nix {};
   signal-desktop = handleTest ./signal-desktop.nix {};
   simple = handleTest ./simple.nix {};
   slurm = handleTest ./slurm.nix {};
   smokeping = handleTest ./smokeping.nix {};
+  snapcast = handleTest ./snapcast.nix {};
   snapper = handleTest ./snapper.nix {};
+  sogo = handleTest ./sogo.nix {};
   solr = handleTest ./solr.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
   spike = handleTest ./spike.nix {};
   sonarr = handleTest ./sonarr.nix {};
+  sslh = handleTest ./sslh.nix {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   sudo = handleTest ./sudo.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
+  syncthing = handleTest ./syncthing.nix {};
   syncthing-init = handleTest ./syncthing-init.nix {};
   syncthing-relay = handleTest ./syncthing-relay.nix {};
   systemd = handleTest ./systemd.nix {};
   systemd-analyze = handleTest ./systemd-analyze.nix {};
-  systemd-boot = handleTestOn ["x86_64-linux"] ./systemd-boot.nix {};
+  systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
+  systemd-boot = handleTest ./systemd-boot.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
   systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
@@ -331,10 +360,8 @@ in
   vault = handleTest ./vault.nix {};
   victoriametrics = handleTest ./victoriametrics.nix {};
   virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
-  wg-quick = handleTest ./wireguard/wg-quick.nix {};
+  wasabibackend = handleTest ./wasabibackend.nix {};
   wireguard = handleTest ./wireguard {};
-  wireguard-generated = handleTest ./wireguard/generated.nix {};
-  wireguard-namespaces = handleTest ./wireguard/namespaces.nix {};
   wordpress = handleTest ./wordpress.nix {};
   xandikos = handleTest ./xandikos.nix {};
   xautolock = handleTest ./xautolock.nix {};
@@ -345,6 +372,8 @@ in
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
   zfs = handleTest ./zfs.nix {};
-  zsh-history = handleTest ./zsh-history.nix {};
+  zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
+  zoneminder = handleTest ./zoneminder.nix {};
   zookeeper = handleTest ./zookeeper.nix {};
+  zsh-history = handleTest ./zsh-history.nix {};
 }
diff --git a/nixos/tests/bazarr.nix b/nixos/tests/bazarr.nix
new file mode 100644
index 00000000000..b8cd8ef38b4
--- /dev/null
+++ b/nixos/tests/bazarr.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+let
+  port = 42069;
+in
+{
+  name = "bazarr";
+  meta.maintainers = with maintainers; [ xwvvvvwx ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.bazarr = {
+        enable = true;
+        listenPort = port;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("bazarr.service")
+    machine.wait_for_open_port("${toString port}")
+    machine.succeed("curl --fail http://localhost:${toString port}/")
+  '';
+})
diff --git a/nixos/tests/bcachefs.nix b/nixos/tests/bcachefs.nix
index 0541e580322..3f116d7df92 100644
--- a/nixos/tests/bcachefs.nix
+++ b/nixos/tests/bcachefs.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.succeed("modprobe bcachefs")
     machine.succeed("bcachefs version")
     machine.succeed("ls /dev")
-    
+
     machine.succeed(
         "mkdir /tmp/mnt",
         "udevadm settle",
diff --git a/nixos/tests/bitcoind.nix b/nixos/tests/bitcoind.nix
new file mode 100644
index 00000000000..09f3e4a6ec0
--- /dev/null
+++ b/nixos/tests/bitcoind.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bitcoind";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  machine = { ... }: {
+    services.bitcoind."mainnet" = {
+      enable = true;
+      rpc = {
+        port = 8332;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+        users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
+      };
+    };
+    services.bitcoind."testnet" = {
+      enable = true;
+      configFile = "/test.blank";
+      testnet = true;
+      rpc = {
+        port = 18332;
+      };
+      extraCmdlineOptions = [ "-rpcuser=rpc" "-rpcpassword=rpc" "-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225" ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("bitcoind-mainnet.service")
+    machine.wait_for_unit("bitcoind-testnet.service")
+
+    machine.wait_until_succeeds(
+        'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+  '';
+})
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 0a97d5556a2..c195b60cd56 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -19,6 +19,7 @@ let
   externalClient2Address = "80.100.100.2";
   externalTrackerAddress = "80.100.100.3";
 
+  download-dir = "/var/lib/transmission/Downloads";
   transmissionConfig = { ... }: {
     environment.systemPackages = [ pkgs.transmission ];
     services.transmission = {
@@ -26,6 +27,7 @@ let
       settings = {
         dht-enabled = false;
         message-level = 3;
+        inherit download-dir;
       };
     };
   };
@@ -117,12 +119,12 @@ in
       router.wait_for_unit("miniupnpd")
 
       # Create the torrent.
-      tracker.succeed("mkdir /tmp/data")
+      tracker.succeed("mkdir ${download-dir}/data")
       tracker.succeed(
-          "cp ${file} /tmp/data/test.tar.bz2"
+          "cp ${file} ${download-dir}/data/test.tar.bz2"
       )
       tracker.succeed(
-          "transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
+          "transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
       )
       tracker.succeed("chmod 644 /tmp/test.torrent")
 
@@ -133,18 +135,16 @@ in
 
       # Start the initial seeder.
       tracker.succeed(
-          "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data"
+          "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
       )
 
       # Now we should be able to download from the client behind the NAT.
       tracker.wait_for_unit("httpd")
       client1.wait_for_unit("network-online.target")
+      client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
+      client1.wait_for_file("${download-dir}/test.tar.bz2")
       client1.succeed(
-          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &"
-      )
-      client1.wait_for_file("/tmp/test.tar.bz2")
-      client1.succeed(
-          "cmp /tmp/test.tar.bz2 ${file}"
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
       )
 
       # Bring down the initial seeder.
@@ -154,11 +154,11 @@ in
       # the first client created a NAT hole in the router.
       client2.wait_for_unit("network-online.target")
       client2.succeed(
-          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &"
+          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
       )
-      client2.wait_for_file("/tmp/test.tar.bz2")
+      client2.wait_for_file("${download-dir}/test.tar.bz2")
       client2.succeed(
-          "cmp /tmp/test.tar.bz2 ${file}"
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
       )
     '';
 })
diff --git a/nixos/tests/blockbook-frontend.nix b/nixos/tests/blockbook-frontend.nix
new file mode 100644
index 00000000000..742a02999e7
--- /dev/null
+++ b/nixos/tests/blockbook-frontend.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "blockbook-frontend";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  machine = { ... }: {
+    services.blockbook-frontend."test" = {
+      enable = true;
+    };
+    services.bitcoind.mainnet = {
+      enable = true;
+      rpc = {
+        port = 8030;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("blockbook-frontend-test.service")
+
+    machine.wait_for_open_port(9030)
+
+    machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
+  '';
+})
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
index d97471e293e..bf37eb8607b 100644
--- a/nixos/tests/borgbackup.nix
+++ b/nixos/tests/borgbackup.nix
@@ -43,7 +43,7 @@ in {
   nodes = {
     client = { ... }: {
       services.borgbackup.jobs = {
-        
+
         local = {
           paths = dataDir;
           repo = localRepo;
diff --git a/nixos/tests/ceph-multi-node.nix b/nixos/tests/ceph-multi-node.nix
index 22fe5cada48..e26c6d5d670 100644
--- a/nixos/tests/ceph-multi-node.nix
+++ b/nixos/tests/ceph-multi-node.nix
@@ -183,15 +183,15 @@ let
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
 
     monA.succeed(
-        "ceph osd pool create multi-node-test 128 128",
+        "ceph osd pool create multi-node-test 32 32",
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool rename multi-node-test multi-node-other-test",
         "ceph osd pool ls | grep 'multi-node-other-test'",
     )
-    monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'")
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
     monA.succeed("ceph osd pool set multi-node-other-test size 2")
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
-    monA.wait_until_succeeds("ceph -s | grep '128 active+clean'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
     monA.fail(
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
diff --git a/nixos/tests/ceph-single-node.nix b/nixos/tests/ceph-single-node.nix
index 01c4b413845..98528f6317b 100644
--- a/nixos/tests/ceph-single-node.nix
+++ b/nixos/tests/ceph-single-node.nix
@@ -143,12 +143,12 @@ let
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
 
     monA.succeed(
-        "ceph osd pool create single-node-test 128 128",
+        "ceph osd pool create single-node-test 32 32",
         "ceph osd pool ls | grep 'single-node-test'",
         "ceph osd pool rename single-node-test single-node-other-test",
         "ceph osd pool ls | grep 'single-node-other-test'",
     )
-    monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'")
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
     monA.succeed(
         "ceph osd getcrushmap -o crush",
         "crushtool -d crush -o decrushed",
@@ -158,7 +158,7 @@ let
         "ceph osd pool set single-node-other-test size 2",
     )
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
-    monA.wait_until_succeeds("ceph -s | grep '128 active+clean'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
     monA.fail(
         "ceph osd pool ls | grep 'multi-node-test'",
         "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
diff --git a/nixos/tests/common/auto.nix b/nixos/tests/common/auto.nix
index 2c21a8d5167..da6b14e9f16 100644
--- a/nixos/tests/common/auto.nix
+++ b/nixos/tests/common/auto.nix
@@ -41,8 +41,8 @@ in
 
   config = mkIf cfg.enable {
 
-    services.xserver.displayManager.lightdm = {
-      enable = true;
+    services.xserver.displayManager = {
+      lightdm.enable = true;
       autoLogin = {
         enable = true;
         user = cfg.user;
diff --git a/nixos/tests/consul.nix b/nixos/tests/consul.nix
index 6600dae4770..ee85f1d0b91 100644
--- a/nixos/tests/consul.nix
+++ b/nixos/tests/consul.nix
@@ -55,30 +55,33 @@ let
 
   server = index: { pkgs, ... }:
     let
-      ip = builtins.elemAt allConsensusServerHosts index;
+      numConsensusServers = builtins.length allConsensusServerHosts;
+      thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
+      ip = thisConsensusServerHost; # since we already use IPs to identify servers
     in
       {
         networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
-          { address = builtins.elemAt allConsensusServerHosts index; prefixLength = 16; }
+          { address = ip; prefixLength = 16; }
         ];
         networking.firewall = firewallSettings;
 
         services.consul =
-          let
-            thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
-          in
           assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
           {
             enable = true;
             inherit webUi;
             extraConfig = defaultExtraConfig // {
               server = true;
-              bootstrap_expect = builtins.length allConsensusServerHosts;
+              bootstrap_expect = numConsensusServers;
+              # Tell Consul that we never intend to drop below this many servers.
+              # Ensures to not permanently lose consensus after temporary loss.
+              # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+              autopilot.min_quorum = numConsensusServers;
               retry_join =
                 # If there's only 1 node in the network, we allow self-join;
                 # otherwise, the node must not try to join itself, and join only the other servers.
                 # See https://github.com/hashicorp/consul/issues/2868
-                if builtins.length allConsensusServerHosts == 1
+                if numConsensusServers == 1
                   then allConsensusServerHosts
                   else builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
               bind_addr = ip;
@@ -104,40 +107,123 @@ in {
     for m in machines:
         m.wait_for_unit("consul.service")
 
-    for m in machines:
-        m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+
+    def wait_for_healthy_servers():
+        # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+        # for why the `Voter` column of `list-peers` has that info.
+        # TODO: The `grep true` relies on the fact that currently in
+        #       the output like
+        #           # consul operator raft list-peers
+        #           Node     ID   Address           State     Voter  RaftProtocol
+        #           server3  ...  192.168.1.3:8300  leader    true   3
+        #           server2  ...  192.168.1.2:8300  follower  true   3
+        #           server1  ...  192.168.1.1:8300  follower  false  3
+        #       `Voter`is the only boolean column.
+        #       Change this to the more reliable way to be defined by
+        #       https://github.com/hashicorp/consul/issues/8118
+        #       once that ticket is closed.
+        for m in machines:
+            m.wait_until_succeeds(
+                "[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
+            )
+
+
+    def wait_for_all_machines_alive():
+        """
+        Note that Serf-"alive" does not mean "Raft"-healthy;
+        see `wait_for_healthy_servers()` for that instead.
+        """
+        for m in machines:
+            m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+
+
+    wait_for_healthy_servers()
+    # Also wait for clients to be alive.
+    wait_for_all_machines_alive()
 
     client1.succeed("consul kv put testkey 42")
     client2.succeed("[ $(consul kv get testkey) == 42 ]")
 
-    # Test that the cluster can tolearate failures of any single server:
-    for server in servers:
-        server.crash()
 
-        # For each client, wait until they have connection again
-        # using `kv get -recurse` before issuing commands.
-        client1.wait_until_succeeds("consul kv get -recurse")
-        client2.wait_until_succeeds("consul kv get -recurse")
+    def rolling_reboot_test(proper_rolling_procedure=True):
+        """
+        Tests that the cluster can tolearate failures of any single server,
+        following the recommended rolling upgrade procedure from
+        https://www.consul.io/docs/upgrading#standard-upgrades.
 
-        # Do some consul actions while one server is down.
-        client1.succeed("consul kv put testkey 43")
-        client2.succeed("[ $(consul kv get testkey) == 43 ]")
-        client2.succeed("consul kv delete testkey")
+        Optionally, `proper_rolling_procedure=False` can be given
+        to wait only for each server to be back `Healthy`, not `Stable`
+        in the Raft consensus, see Consul setting `ServerStabilizationTime` and
+        https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
+        """
+
+        for server in servers:
+            server.crash()
+
+            # For each client, wait until they have connection again
+            # using `kv get -recurse` before issuing commands.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
 
-        # Restart crashed machine.
-        server.start()
+            # Do some consul actions while one server is down.
+            client1.succeed("consul kv put testkey 43")
+            client2.succeed("[ $(consul kv get testkey) == 43 ]")
+            client2.succeed("consul kv delete testkey")
+
+            # Restart crashed machine.
+            server.start()
+
+            if proper_rolling_procedure:
+                # Wait for recovery.
+                wait_for_healthy_servers()
+            else:
+                # NOT proper rolling upgrade procedure, see above.
+                wait_for_all_machines_alive()
+
+            # Wait for client connections.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
+
+            # Do some consul actions with server back up.
+            client1.succeed("consul kv put testkey 44")
+            client2.succeed("[ $(consul kv get testkey) == 44 ]")
+            client2.succeed("consul kv delete testkey")
+
+
+    def all_servers_crash_simultaneously_test():
+        """
+        Tests that the cluster will eventually come back after all
+        servers crash simultaneously.
+        """
+
+        for server in servers:
+            server.crash()
+
+        for server in servers:
+            server.start()
 
         # Wait for recovery.
-        for m in machines:
-            m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+        wait_for_healthy_servers()
 
         # Wait for client connections.
         client1.wait_until_succeeds("consul kv get -recurse")
         client2.wait_until_succeeds("consul kv get -recurse")
 
-        # Do some consul actions with server back up.
+        # Do some consul actions with servers back up.
         client1.succeed("consul kv put testkey 44")
         client2.succeed("[ $(consul kv get testkey) == 44 ]")
         client2.succeed("consul kv delete testkey")
+
+
+    # Run the tests.
+
+    print("rolling_reboot_test()")
+    rolling_reboot_test()
+
+    print("all_servers_crash_simultaneously_test()")
+    all_servers_crash_simultaneously_test()
+
+    print("rolling_reboot_test(proper_rolling_procedure=False)")
+    rolling_reboot_test(proper_rolling_procedure=False)
   '';
 })
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index fc90e151bd9..1e2c2c6c374 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -5,7 +5,7 @@ let
   hostPort = 10080;
   containerIp = "192.168.0.100";
   containerPort = 80;
-in 
+in
 
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "containers-portforward";
diff --git a/nixos/tests/convos.nix b/nixos/tests/convos.nix
new file mode 100644
index 00000000000..b4ff1188fd8
--- /dev/null
+++ b/nixos/tests/convos.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+with lib;
+let
+  port = 3333;
+in
+{
+  name = "convos";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes = {
+    machine =
+      { pkgs, ... }:
+      {
+        services.convos = {
+          enable = true;
+          listenPort = port;
+        };
+      };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("convos")
+    machine.wait_for_open_port("${toString port}")
+    machine.succeed("journalctl -u convos | grep -q 'Listening at.*${toString port}'")
+    machine.succeed("curl http://localhost:${toString port}/")
+  '';
+})
diff --git a/nixos/tests/corerad.nix b/nixos/tests/corerad.nix
index 741fa448f68..37a1e90477a 100644
--- a/nixos/tests/corerad.nix
+++ b/nixos/tests/corerad.nix
@@ -1,9 +1,9 @@
 import ./make-test-python.nix (
   {
     nodes = {
-      router = {config, pkgs, ...}: { 
+      router = {config, pkgs, ...}: {
         config = {
-          # This machines simulates a router with IPv6 forwarding and a static IPv6 address.
+          # This machine simulates a router with IPv6 forwarding and a static IPv6 address.
           boot.kernel.sysctl = {
             "net.ipv6.conf.all.forwarding" = true;
           };
@@ -14,13 +14,25 @@ import ./make-test-python.nix (
             enable = true;
             # Serve router advertisements to the client machine with prefix information matching
             # any IPv6 /64 prefixes configured on this interface.
-            configFile = pkgs.writeText "corerad.toml" ''
-              [[interfaces]]
-              name = "eth1"
-              advertise = true
-                [[interfaces.prefix]]
-                prefix = "::/64"
-            '';
+            #
+            # This configuration is identical to the example in the CoreRAD NixOS module.
+            settings = {
+              interfaces = [
+                {
+                  name = "eth0";
+                  monitor = true;
+                }
+                {
+                  name = "eth1";
+                  advertise = true;
+                  prefix = [{ prefix = "::/64"; }];
+                }
+              ];
+              debug = {
+                address = "localhost:9430";
+                prometheus = true;
+              };
+            };
           };
         };
       };
@@ -66,5 +78,12 @@ import ./make-test-python.nix (
           assert (
               "/64 scope global temporary" in addrs
           ), "SLAAC temporary address was not configured on client after router advertisement"
+
+      with subtest("Verify HTTP debug server is configured"):
+          out = router.succeed("curl localhost:9430/metrics")
+
+          assert (
+              "corerad_build_info" in out
+          ), "Build info metric was not found in Prometheus output"
     '';
   })
diff --git a/nixos/tests/dnscrypt-wrapper/default.nix b/nixos/tests/dnscrypt-wrapper/default.nix
new file mode 100644
index 00000000000..1dc925f4de7
--- /dev/null
+++ b/nixos/tests/dnscrypt-wrapper/default.nix
@@ -0,0 +1,71 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "dnscrypt-wrapper";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes = {
+    server = { lib, ... }:
+      { services.dnscrypt-wrapper = with builtins;
+          { enable = true;
+            address = "192.168.1.1";
+            keys.expiration = 5; # days
+            keys.checkInterval = 2;  # min
+            # The keypair was generated by the command:
+            # dnscrypt-wrapper --gen-provider-keypair \
+            #  --provider-name=2.dnscrypt-cert.server \
+            #  --ext-address=192.168.1.1:5353
+            providerKey.public = toFile "public.key" (readFile ./public.key);
+            providerKey.secret = toFile "secret.key" (readFile ./secret.key);
+          };
+        services.tinydns.enable = true;
+        services.tinydns.data = ''
+          ..:192.168.1.1:a
+          +it.works:1.2.3.4
+        '';
+        networking.firewall.allowedUDPPorts = [ 5353 ];
+        networking.firewall.allowedTCPPorts = [ 5353 ];
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
+          [ { address = "192.168.1.1"; prefixLength = 24; } ];
+      };
+
+    client = { lib, ... }:
+      { services.dnscrypt-proxy2.enable = true;
+        services.dnscrypt-proxy2.settings = {
+          server_names = [ "server" ];
+          static.server.stamp = "sdns://AQAAAAAAAAAAEDE5Mi4xNjguMS4xOjUzNTMgFEHYOv0SCKSuqR5CDYa7-58cCBuXO2_5uTSVU9wNQF0WMi5kbnNjcnlwdC1jZXJ0LnNlcnZlcg";
+        };
+        networking.nameservers = [ "127.0.0.1" ];
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
+          [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("The server can generate the ephemeral keypair"):
+        server.wait_for_unit("dnscrypt-wrapper")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt")
+
+    with subtest("The client can connect to the server"):
+        server.wait_for_unit("tinydns")
+        client.wait_for_unit("dnscrypt-proxy2")
+        assert "1.2.3.4" in client.succeed(
+            "host it.works"
+        ), "The IP address of 'it.works' does not match 1.2.3.4"
+
+    with subtest("The server rotates the ephemeral keys"):
+        # advance time by a little less than 5 days
+        server.succeed("date -s \"$(date --date '4 days 6 hours')\"")
+        client.succeed("date -s \"$(date --date '4 days 6 hours')\"")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys")
+
+    with subtest("The client can still connect to the server"):
+        server.wait_for_unit("dnscrypt-wrapper")
+        client.succeed("host it.works")
+  '';
+})
+
diff --git a/nixos/tests/dnscrypt-wrapper/public.key b/nixos/tests/dnscrypt-wrapper/public.key
new file mode 100644
index 00000000000..80232b97f52
--- /dev/null
+++ b/nixos/tests/dnscrypt-wrapper/public.key
@@ -0,0 +1 @@
+AØ:ý¤®©B
†»ûŸ—;où¹4•SÜ
@]
\ No newline at end of file
diff --git a/nixos/tests/dnscrypt-wrapper/secret.key b/nixos/tests/dnscrypt-wrapper/secret.key
new file mode 100644
index 00000000000..01fbf8e08b7
--- /dev/null
+++ b/nixos/tests/dnscrypt-wrapper/secret.key
@@ -0,0 +1 @@
+G½>Æ©» ì>Ðà¥(Ò²‡¼J•«º=Ÿ„ÝÁlìAØ:ý¤®©B
†»ûŸ—;où¹4•SÜ
@]
\ No newline at end of file
diff --git a/nixos/tests/docker-preloader.nix b/nixos/tests/docker-preloader.nix
index eeedec9a392..c3e8aced351 100644
--- a/nixos/tests/docker-preloader.nix
+++ b/nixos/tests/docker-preloader.nix
@@ -16,10 +16,10 @@ import ./make-test.nix ({ pkgs, ...} : {
           services.openssh.extraConfig = "PermitEmptyPasswords yes";
           users.extraUsers.root.password = "";
         };
-  };    
+  };
   testScript = ''
     startAll;
-    
+
     $docker->waitForUnit("sockets.target");
     $docker->succeed("docker run nix nix-store --version");
     $docker->succeed("docker run bash bash --version");
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 51b472fcf9c..2543801ae8b 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -30,8 +30,45 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         )
 
     docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
+    # Check imageTag attribute matches image
+    docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.bash.imageTag}'")
     docker.succeed("docker rmi ${examples.bash.imageName}")
 
+    # The remaining combinations
+    with subtest("Ensure imageTag attribute matches image"):
+        docker.succeed(
+            "docker load --input='${examples.bashNoTag}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTag.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
+
+        docker.succeed(
+            "docker load --input='${examples.bashNoTagLayered}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagLayered.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTagLayered.imageName}:${examples.bashNoTagLayered.imageTag}")
+
+        docker.succeed(
+            "${examples.bashNoTagStreamLayered} | docker load"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagStreamLayered.imageTag}'"
+        )
+        docker.succeed(
+            "docker rmi ${examples.bashNoTagStreamLayered.imageName}:${examples.bashNoTagStreamLayered.imageTag}"
+        )
+
+        docker.succeed(
+            "docker load --input='${examples.nixLayered}'"
+        )
+        docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.nixLayered.imageTag}'")
+        docker.succeed("docker rmi ${examples.nixLayered.imageName}")
+
+
     with subtest(
         "Check if the nix store is correctly initialized by listing "
         "dependencies of the installed Nix binary"
@@ -42,6 +79,30 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker rmi ${examples.nix.imageName}",
         )
 
+    with subtest(
+        "Ensure (layered) nix store has correct permissions "
+        "and that the container starts when its process does not have uid 0"
+    ):
+        docker.succeed(
+            "docker load --input='${examples.bashLayeredWithUser}'",
+            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'",
+            "docker rmi ${examples.bashLayeredWithUser.imageName}",
+        )
+
+    with subtest("The nix binary symlinks are intact"):
+        docker.succeed(
+            "docker load --input='${examples.nix}'",
+            "docker run --rm ${examples.nix.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
+            "docker rmi ${examples.nix.imageName}",
+        )
+
+    with subtest("The nix binary symlinks are intact when the image is layered"):
+        docker.succeed(
+            "docker load --input='${examples.nixLayered}'",
+            "docker run --rm ${examples.nixLayered.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
+            "docker rmi ${examples.nixLayered.imageName}",
+        )
+
     with subtest("The pullImage tool works"):
         docker.succeed(
             "docker load --input='${examples.nixFromDockerHub}'",
@@ -76,13 +137,22 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     with subtest("Ensure Docker images can use an unstable date"):
         docker.succeed(
-            "docker load --input='${examples.bash}'"
+            "docker load --input='${examples.unstableDate}'"
         )
         assert unix_time_second1 not in docker.succeed(
             "docker inspect ${examples.unstableDate.imageName} "
             + "| ${pkgs.jq}/bin/jq -r .[].Created"
         )
 
+    with subtest("Ensure Layered Docker images can use an unstable date"):
+        docker.succeed(
+            "docker load --input='${examples.unstableDateLayered}'"
+        )
+        assert unix_time_second1 not in docker.succeed(
+            "docker inspect ${examples.unstableDateLayered.imageName} "
+            + "| ${pkgs.jq}/bin/jq -r .[].Created"
+        )
+
     with subtest("Ensure Layered Docker images work"):
         docker.succeed(
             "docker load --input='${examples.layered-image}'",
@@ -124,6 +194,16 @@ import ./make-test-python.nix ({ pkgs, ... }: {
                 f"docker run --rm  ${examples.layersOrder.imageName} cat /tmp/layer{index}"
             )
 
+    with subtest("Ensure environment variables are correctly inherited"):
+        docker.succeed(
+            "docker load --input='${examples.environmentVariables}'"
+        )
+        out = docker.succeed("docker run --rm ${examples.environmentVariables.imageName} env")
+        env = out.splitlines()
+        assert "FROM_PARENT=true" in env, "envvars from the parent should be preserved"
+        assert "FROM_CHILD=true" in env, "envvars from the child should be preserved"
+        assert "LAST_LAYER=child" in env, "envvars from the child should take priority"
+
     with subtest("Ensure image with only 2 layers can be loaded"):
         docker.succeed(
             "docker load --input='${examples.two-layered-image}'"
@@ -154,5 +234,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         # This check may be loosened to allow an *empty* store rather than *no* store.
         docker.succeed("docker run --rm no-store-paths ls /")
         docker.fail("docker run --rm no-store-paths ls /nix/store")
+
+    with subtest("Ensure buildLayeredImage does not change store path contents."):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.filesInStore}'",
+            "docker run --rm file-in-store nix-store --verify --check-contents",
+            "docker run --rm file-in-store |& grep 'some data'",
+        )
   '';
 })
diff --git a/nixos/tests/docker.nix b/nixos/tests/docker.nix
index 8fda7c1395e..a4a61468f33 100644
--- a/nixos/tests/docker.nix
+++ b/nixos/tests/docker.nix
@@ -43,7 +43,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     docker.fail("sudo -u noprivs docker ps")
     docker.succeed("docker stop sleeping")
 
-    # Must match version twice to ensure client and server versions are correct
-    docker.succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "2" ]')
+    # Must match version 4 times to ensure client and server git commits and versions are correct
+    docker.succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "4" ]')
   '';
 })
diff --git a/nixos/tests/dokuwiki.nix b/nixos/tests/dokuwiki.nix
index 05271919eff..58069366ca3 100644
--- a/nixos/tests/dokuwiki.nix
+++ b/nixos/tests/dokuwiki.nix
@@ -32,24 +32,17 @@ let
 
 in {
   name = "dokuwiki";
-  meta.maintainers = with pkgs.lib.maintainers; [ "1000101" ];
-
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
   machine = { ... }: {
     services.dokuwiki."site1.local" = {
       aclUse = false;
       superUser = "admin";
-      nginx = {
-        forceSSL = false;
-        enableACME = false;
-      };
     };
     services.dokuwiki."site2.local" = {
-      aclUse = true;
+      usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
       superUser = "admin";
-      nginx = {
-        forceSSL = false;
-        enableACME = false;
-      };
       templates = [ template-bootstrap3 ];
       plugins = [ plugin-icalevents ];
     };
@@ -69,6 +62,15 @@ in {
     machine.wait_for_open_port(80)
 
     machine.succeed("curl -sSfL http://site1.local/ | grep 'DokuWiki'")
+    machine.fail("curl -sSfL 'http://site1.local/doku.php?do=login' | grep 'Login'")
+
     machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki'")
+    machine.succeed("curl -sSfL 'http://site2.local/doku.php?do=login' | grep 'Login'")
+
+    machine.succeed(
+        "echo 'admin:$2y$10$ijdBQMzSVV20SrKtCna8gue36vnsbVm2wItAXvdm876sshI4uwy6S:Admin:admin@example.test:user' >> /var/lib/dokuwiki/site2.local/users.auth.php",
+        "curl -sSfL -d 'u=admin&p=password' --cookie-jar cjar 'http://site2.local/doku.php?do=login'",
+        "curl -sSfL --cookie cjar --cookie-jar cjar 'http://site2.local/doku.php?do=login' | grep 'Logged in as: <bdi>Admin</bdi>'",
+    )
   '';
 })
diff --git a/nixos/tests/engelsystem.nix b/nixos/tests/engelsystem.nix
new file mode 100644
index 00000000000..39c10718093
--- /dev/null
+++ b/nixos/tests/engelsystem.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  {
+    name = "engelsystem";
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ talyz ];
+    };
+
+    nodes.engelsystem =
+      { ... }:
+      {
+        services.engelsystem = {
+          enable = true;
+          domain = "engelsystem";
+          createDatabase = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 80 443 ];
+        environment.systemPackages = with pkgs; [
+          xmlstarlet
+          libxml2
+        ];
+      };
+
+    testScript = ''
+      engelsystem.start()
+      engelsystem.wait_for_unit("phpfpm-engelsystem.service")
+      engelsystem.wait_until_succeeds("curl engelsystem/login -sS -f")
+      engelsystem.succeed(
+          "curl engelsystem/login -sS -f -c cookie | xmllint -html -xmlout - >login"
+      )
+      engelsystem.succeed(
+          "xml sel -T -t -m \"html/head/meta[@name='csrf-token']\" -v @content login >token"
+      )
+      engelsystem.succeed(
+          "curl engelsystem/login -sS -f -b cookie -F 'login=admin' -F 'password=asdfasdf' -F '_token=<token' -L | xmllint -html -xmlout - >news"
+      )
+      engelsystem.succeed(
+          "test 'News - Engelsystem' = \"$(xml sel -T -t -c html/head/title news)\""
+      )
+    '';
+  })
diff --git a/nixos/tests/enlightenment.nix b/nixos/tests/enlightenment.nix
index 5fa8d765dd1..0132b98b1cb 100644
--- a/nixos/tests/enlightenment.nix
+++ b/nixos/tests/enlightenment.nix
@@ -41,28 +41,24 @@ import ./make-test-python.nix ({ pkgs, ...} :
 
     with subtest("First time wizard"):
         machine.wait_for_text("Default")  # Language
-        machine.succeed("xdotool mousemove 512 185 click 1")  # Default Language
         machine.screenshot("wizard1")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
-
-        machine.wait_for_text("English")  # Keyboard (default)
         machine.screenshot("wizard2")
-        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("Standard")  # Profile (default)
+        machine.wait_for_text("English")  # Keyboard (default)
         machine.screenshot("wizard3")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("Title")  # Sizing (default)
+        machine.wait_for_text("Standard")  # Profile (default)
         machine.screenshot("wizard4")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("clicked")  # Windows Phocus
-        machine.succeed("xdotool mousemove 512 370 click 1")  # Click
+        machine.wait_for_text("Title")  # Sizing (default)
         machine.screenshot("wizard5")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("bindings")  # Mouse Modifiers (default)
+        machine.wait_for_text("clicked")  # Windows Focus
+        machine.succeed("xdotool mousemove 512 370 click 1")  # Click
         machine.screenshot("wizard6")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
@@ -74,7 +70,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.screenshot("wizard8")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("Compositing")  # Compositing (default)
+        machine.wait_for_text("OpenGL")  # Compositing (default)
         machine.screenshot("wizard9")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
diff --git a/nixos/tests/ergo.nix b/nixos/tests/ergo.nix
new file mode 100644
index 00000000000..8cdbbf62a95
--- /dev/null
+++ b/nixos/tests/ergo.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ergo";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.ergo.enable = true;
+      services.ergo.api.keyHash = "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("ergo.service")
+  '';
+})
diff --git a/nixos/tests/firejail.nix b/nixos/tests/firejail.nix
new file mode 100644
index 00000000000..a723cb01664
--- /dev/null
+++ b/nixos/tests/firejail.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "firejail";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+
+    programs.firejail = {
+      enable = true;
+      wrappedBinaries = {
+        bash-jailed  = "${pkgs.bash}/bin/bash";
+      };
+    };
+
+    systemd.services.setupFirejailTest = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/home/alice";
+      };
+
+      unitConfig = {
+        type = "oneshot";
+        RemainAfterExit = true;
+        user = "alice";
+      };
+
+      script = ''
+        cd $HOME
+
+        mkdir .password-store && echo s3cret > .password-store/secret
+        mkdir my-secrets && echo s3cret > my-secrets/secret
+
+        echo publ1c > public
+
+        mkdir -p .config/firejail
+        echo 'blacklist ''${HOME}/my-secrets' > .config/firejail/globals.local
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # Test path acl with wrapper
+    machine.succeed("sudo -u alice bash-jailed -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice bash-jailed -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail("sudo -u alice bash-jailed -c 'cat ~/my-secrets/secret' | grep -q s3cret")
+
+
+    # Test path acl with firejail executable
+    machine.succeed("sudo -u alice firejail -- bash -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/my-secrets/secret' | grep -q s3cret"
+    )
+
+    # Disabling profiles
+    machine.succeed(
+        "sudo -u alice bash -c 'firejail --noprofile -- cat ~/.password-store/secret' | grep -q s3cret"
+    )
+
+    # CVE-2020-17367
+    machine.fail(
+        "sudo -u alice firejail --private-tmp id --output=/tmp/vuln1 && cat /tmp/vuln1"
+    )
+
+    # CVE-2020-17368
+    machine.fail(
+        "sudo -u alice firejail --private-tmp --output=/tmp/foo 'bash -c $(id>/tmp/vuln2;echo id)' && cat /tmp/vuln2"
+    )
+  '';
+})
+
diff --git a/nixos/tests/gnome3-xorg.nix b/nixos/tests/gnome3-xorg.nix
index b59badcd5de..0d05c12384f 100644
--- a/nixos/tests/gnome3-xorg.nix
+++ b/nixos/tests/gnome3-xorg.nix
@@ -12,8 +12,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.gdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
         autoLogin = {
           enable = true;
           user = user.name;
@@ -21,6 +22,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       };
 
       services.xserver.desktopManager.gnome3.enable = true;
+      services.xserver.desktopManager.gnome3.debug = true;
       services.xserver.displayManager.defaultSession = "gnome-xorg";
 
       virtualisation.memorySize = 1024;
diff --git a/nixos/tests/gnome3.nix b/nixos/tests/gnome3.nix
index 17e72c5f651..b3d7aff8bd7 100644
--- a/nixos/tests/gnome3.nix
+++ b/nixos/tests/gnome3.nix
@@ -11,8 +11,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.gdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
         autoLogin = {
           enable = true;
           user = "alice";
@@ -20,6 +21,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       };
 
       services.xserver.desktopManager.gnome3.enable = true;
+      services.xserver.desktopManager.gnome3.debug = true;
 
       virtualisation.memorySize = 1024;
     };
diff --git a/nixos/tests/go-neb.nix b/nixos/tests/go-neb.nix
new file mode 100644
index 00000000000..d9e5db0b4a5
--- /dev/null
+++ b/nixos/tests/go-neb.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "go-neb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hexa maralorn ];
+  };
+
+  nodes = {
+    server = {
+      services.go-neb = {
+        enable = true;
+        baseUrl = "http://localhost";
+        config = {
+          clients = [ {
+            UserId = "@test:localhost";
+            AccessToken = "changeme";
+            HomeServerUrl = "http://localhost";
+            Sync = false;
+            AutoJoinRooms = false;
+            DisplayName = "neverbeseen";
+          } ];
+          services = [ {
+            ID = "wikipedia_service";
+            Type = "wikipedia";
+            UserID = "@test:localhost";
+            Config = { };
+          } ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("go-neb.service")
+    server.wait_until_succeeds(
+        "curl -L http://localhost:4050/services/hooks/d2lraXBlZGlhX3NlcnZpY2U"
+    )
+    server.wait_until_succeeds(
+        "journalctl -eu go-neb -o cat | grep -q service_id=wikipedia_service"
+    )
+  '';
+
+})
diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix
index 71776a94cbd..137be2d89c8 100644
--- a/nixos/tests/graphite.nix
+++ b/nixos/tests/graphite.nix
@@ -1,11 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... } :
 {
   name = "graphite";
-  meta = {
-    # Fails on dependency `python-2.7-Twisted`'s test suite
-    # complaining `ImportError: No module named zope.interface`.
-    broken = true;
-  };
   nodes = {
     one =
       { ... }: {
@@ -21,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... } :
           api = {
             enable = true;
             port = 8082;
-            finders = [ pkgs.python3Packages.influxgraph ];
+            finders = [ ];
           };
           carbon.enableCache = true;
           seyren.enable = false;  # Implicitely requires openssl-1.0.2u which is marked insecure
@@ -41,10 +36,14 @@ import ./make-test-python.nix ({ pkgs, ... } :
     # even if they're still in preStart (which takes quite long for graphiteWeb).
     # Wait for ports to open so we're sure the services are up and listening.
     one.wait_for_open_port(8080)
+    one.wait_for_open_port(8082)
     one.wait_for_open_port(2003)
     one.succeed('echo "foo 1 `date +%s`" | nc -N localhost 2003')
     one.wait_until_succeeds(
         "curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
     )
+    one.wait_until_succeeds(
+        "curl 'http://localhost:8082/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
+    )
   '';
 })
diff --git a/nixos/tests/grub.nix b/nixos/tests/grub.nix
new file mode 100644
index 00000000000..84bfc90955b
--- /dev/null
+++ b/nixos/tests/grub.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "grub";
+
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  machine = { ... }: {
+    virtualisation.useBootLoader = true;
+
+    boot.loader.timeout = null;
+    boot.loader.grub = {
+      enable = true;
+      users.alice.password = "supersecret";
+
+      # OCR is not accurate enough
+      extraConfig = "serial; terminal_output serial";
+    };
+  };
+
+  testScript = ''
+    def grub_login_as(user, password):
+        """
+        Enters user and password to log into GRUB
+        """
+        machine.wait_for_console_text("Enter username:")
+        machine.send_chars(user + "\n")
+        machine.wait_for_console_text("Enter password:")
+        machine.send_chars(password + "\n")
+
+
+    def grub_select_all_configurations():
+        """
+        Selects "All configurations" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+
+    machine.start()
+
+    # wait for grub screen
+    machine.wait_for_console_text("GNU GRUB")
+
+    grub_select_all_configurations()
+    with subtest("Invalid credentials are rejected"):
+        grub_login_as("wronguser", "wrongsecret")
+        machine.wait_for_console_text("error: access denied.")
+
+    grub_select_all_configurations()
+    with subtest("Valid credentials are accepted"):
+        grub_login_as("alice", "supersecret")
+        machine.send_chars("\n")  # press enter to boot
+        machine.wait_for_console_text("Linux version")
+
+    with subtest("Machine boots correctly"):
+        machine.wait_for_unit("multi-user.target")
+  '';
+})
diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix
index 79f34b07faf..ffb77c052a2 100644
--- a/nixos/tests/haproxy.nix
+++ b/nixos/tests/haproxy.nix
@@ -43,5 +43,13 @@ import ./make-test-python.nix ({ pkgs, ...}: {
     assert "haproxy_process_pool_allocated_bytes" in machine.succeed(
         "curl -k http://localhost:80/metrics"
     )
+
+    with subtest("reload"):
+        machine.succeed("systemctl reload haproxy")
+        # wait some time to ensure the following request hits the reloaded haproxy
+        machine.sleep(5)
+        assert "We are all good!" in machine.succeed(
+            "curl -k http://localhost:80/index.txt"
+        )
   '';
 })
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 80dca43f1f3..a93a28d877a 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -2,69 +2,66 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
 let
   configDir = "/var/lib/foobar";
-  apiPassword = "some_secret";
-  mqttPassword = "another_secret";
-  hassCli = "hass-cli --server http://hass:8123 --password '${apiPassword}'";
+  mqttUsername = "homeassistant";
+  mqttPassword = "secret";
 in {
   name = "home-assistant";
   meta = with pkgs.stdenv.lib; {
     maintainers = with maintainers; [ dotlambda ];
   };
 
-  nodes = {
-    hass =
-      { pkgs, ... }:
-      {
-        environment.systemPackages = with pkgs; [
-          mosquitto home-assistant-cli
-        ];
-        services.home-assistant = {
-          inherit configDir;
-          enable = true;
-          package = pkgs.home-assistant.override {
-            extraPackages = ps: with ps; [ hbmqtt ];
-          };
-          config = {
-            homeassistant = {
-              name = "Home";
-              time_zone = "UTC";
-              latitude = "0.0";
-              longitude = "0.0";
-              elevation = 0;
-              auth_providers = [
-                {
-                  type = "legacy_api_password";
-                  api_password = apiPassword;
-                }
-              ];
-            };
-            frontend = { };
-            mqtt = { # Use hbmqtt as broker
-              password = mqttPassword;
-            };
-            binary_sensor = [
-              {
-                platform = "mqtt";
-                state_topic = "home-assistant/test";
-                payload_on = "let_there_be_light";
-                payload_off = "off";
-              }
-            ];
-          };
-          lovelaceConfig = {
-            title = "My Awesome Home";
-            views = [ {
-              title = "Example";
-              cards = [ {
-                type = "markdown";
-                title = "Lovelace";
-                content = "Welcome to your **Lovelace UI**.";
-              } ];
-            } ];
-          };
-          lovelaceConfigWritable = true;
+  nodes.hass = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ mosquitto ];
+    services.mosquitto = {
+      enable = true;
+      users = {
+        "${mqttUsername}" = {
+          acl = [ "pattern readwrite #" ];
+          password = mqttPassword;
         };
       };
+    };
+    services.home-assistant = {
+      inherit configDir;
+      enable = true;
+      config = {
+        homeassistant = {
+          name = "Home";
+          time_zone = "UTC";
+          latitude = "0.0";
+          longitude = "0.0";
+          elevation = 0;
+        };
+        frontend = {};
+        mqtt = {
+          broker = "127.0.0.1";
+          username = mqttUsername;
+          password = mqttPassword;
+        };
+        binary_sensor = [{
+          platform = "mqtt";
+          state_topic = "home-assistant/test";
+          payload_on = "let_there_be_light";
+          payload_off = "off";
+        }];
+        logger = {
+          default = "info";
+          logs."homeassistant.components.mqtt" = "debug";
+        };
+      };
+      lovelaceConfig = {
+        title = "My Awesome Home";
+        views = [{
+          title = "Example";
+          cards = [{
+            type = "markdown";
+            title = "Lovelace";
+            content = "Welcome to your **Lovelace UI**.";
+          }];
+        }];
+      };
+      lovelaceConfigWritable = true;
+    };
   };
 
   testScript = ''
@@ -76,29 +73,14 @@ in {
         hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
     with subtest("Check that Home Assistant's web interface and API can be reached"):
         hass.wait_for_open_port(8123)
-        hass.succeed("curl --fail http://localhost:8123/states")
-        assert "API running" in hass.succeed(
-            "curl --fail -H 'x-ha-access: ${apiPassword}' http://localhost:8123/api/"
-        )
+        hass.succeed("curl --fail http://localhost:8123/lovelace")
     with subtest("Toggle a binary sensor using MQTT"):
-        assert '"state": "off"' in hass.succeed(
-            "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'"
-        )
+        # wait for broker to become available
         hass.wait_until_succeeds(
-            "mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light"
-        )
-        assert '"state": "on"' in hass.succeed(
-            "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'"
-        )
-    with subtest("Toggle a binary sensor using hass-cli"):
-        assert '"state": "on"' in hass.succeed(
-            "${hassCli} --output json state get binary_sensor.mqtt_binary_sensor"
+            "mosquitto_sub -V mqttv311 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -W 1 -t '*'"
         )
         hass.succeed(
-            "${hassCli} state edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'"
-        )
-        assert '"state": "off"' in hass.succeed(
-            "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'"
+            "mosquitto_pub -V mqttv311 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light"
         )
     with subtest("Print log to ease debugging"):
         output_log = hass.succeed("cat ${configDir}/home-assistant.log")
@@ -107,5 +89,9 @@ in {
 
     with subtest("Check that no errors were logged"):
         assert "ERROR" not in output_log
+
+    # example line: 2020-06-20 10:01:32 DEBUG (MainThread) [homeassistant.components.mqtt] Received message on home-assistant/test: b'let_there_be_light'
+    with subtest("Check we received the mosquitto message"):
+        assert "let_there_be_light" in output_log
   '';
 })
diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix
new file mode 100644
index 00000000000..3b87303d73e
--- /dev/null
+++ b/nixos/tests/hostname.nix
@@ -0,0 +1,66 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  makeHostNameTest = hostName: domain:
+    let
+      fqdn = hostName + (optionalString (domain != null) ".${domain}");
+    in
+      makeTest {
+        name = "hostname-${fqdn}";
+        meta = with pkgs.stdenv.lib.maintainers; {
+          maintainers = [ primeos blitz ];
+        };
+
+        machine = { lib, ... }: {
+          networking.hostName = hostName;
+          networking.domain = domain;
+
+          environment.systemPackages = with pkgs; [
+            inetutils
+          ];
+        };
+
+        testScript = ''
+          start_all()
+
+          machine = ${hostName}
+
+          machine.wait_for_unit("network-online.target")
+
+          # The FQDN, domain name, and hostname detection should work as expected:
+          assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
+          assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
+          assert (
+              "${hostName}"
+              == machine.succeed(
+                  'hostnamectl status | grep "Static hostname" | cut -d: -f2'
+              ).strip()
+          )
+
+          # 127.0.0.1 and ::1 should resolve back to "localhost":
+          assert (
+              "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
+          )
+          assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
+
+          # 127.0.0.2 should resolve back to the FQDN and hostname:
+          fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
+          assert (
+              fqdn_and_host_name
+              == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
+          )
+        '';
+      };
+
+in
+{
+  noExplicitDomain = makeHostNameTest "ahost" null;
+
+  explicitDomain = makeHostNameTest "ahost" "adomain";
+}
diff --git a/nixos/tests/hydra/common.nix b/nixos/tests/hydra/common.nix
index f612717dc96..312c52e889a 100644
--- a/nixos/tests/hydra/common.nix
+++ b/nixos/tests/hydra/common.nix
@@ -37,6 +37,7 @@
     };
     services.postfix.enable = true;
     nix = {
+      distributedBuilds = true;
       buildMachines = [{
         hostName = "localhost";
         systems = [ system ];
diff --git a/nixos/tests/hydra/db-migration.nix b/nixos/tests/hydra/db-migration.nix
index cf74acfd67a..ca65e2e66aa 100644
--- a/nixos/tests/hydra/db-migration.nix
+++ b/nixos/tests/hydra/db-migration.nix
@@ -61,7 +61,7 @@ with pkgs.lib;
           'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq'
       )
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id" not in out
 
       original.succeed(
@@ -69,7 +69,7 @@ with pkgs.lib;
       )
       original.wait_for_unit("hydra-init.service")
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id|integer|||" in out
 
       original.succeed("hydra-backfill-ids")
@@ -79,7 +79,7 @@ with pkgs.lib;
       )
       original.wait_for_unit("hydra-init.service")
 
-      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'")
+      out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ builds\" -A'")
       assert "jobset_id|integer||not null|" in out
 
       original.wait_until_succeeds(
diff --git a/nixos/tests/ihatemoney.nix b/nixos/tests/ihatemoney.nix
index 7df0ea0b691..0451a450580 100644
--- a/nixos/tests/ihatemoney.nix
+++ b/nixos/tests/ihatemoney.nix
@@ -1,5 +1,11 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
 let
-  f = backend: import ./make-test-python.nix ({ pkgs, ... }: {
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  f = backend: makeTest {
     name = "ihatemoney-${backend}";
     machine = { lib, ... }: {
       services.ihatemoney = {
@@ -24,9 +30,10 @@ let
     testScript = ''
       machine.wait_for_open_port(8000)
       machine.wait_for_unit("uwsgi.service")
+      machine.wait_until_succeeds("curl http://localhost:8000")
 
       assert '"yay"' in machine.succeed(
-          "curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay\@example.com'"
+          "curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay@example.com'"
       )
       owner, timestamp = machine.succeed(
           "stat --printf %U:%G___%Y /var/lib/ihatemoney/secret_key"
@@ -48,7 +55,7 @@ let
 
       assert "ihatemoney" in machine.succeed("curl http://localhost:8000")
     '';
-  });
+  };
 in {
   ihatemoney-sqlite = f "sqlite";
   ihatemoney-postgresql = f "postgresql";
diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix
new file mode 100644
index 00000000000..bb4c41e6d70
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/default.nix
@@ -0,0 +1,145 @@
+import ../make-test-python.nix ({ lib, ...}:
+
+{
+  name = "initrd-network-openvpn";
+
+  nodes =
+    let
+
+      # Inlining of the shared secret for the
+      # OpenVPN server and client
+      secretblock = ''
+        secret [inline]
+        <secret>
+        ${lib.readFile ./shared.key}
+        </secret>
+        '';
+
+    in
+    {
+
+      # Minimal test case to check a successful boot, even with invalid config
+      minimalboot =
+        { ... }:
+        {
+          boot.initrd.network = {
+            enable = true;
+            openvpn = {
+              enable = true;
+              configuration = "/dev/null";
+            };
+          };
+        };
+
+      # initrd VPN client
+      ovpnclient =
+        { ... }:
+        {
+          virtualisation.useBootLoader = true;
+          virtualisation.vlans = [ 1 ];
+
+          boot.initrd = {
+            # This command does not fork to keep the VM in the state where
+            # only the initramfs is loaded
+            preLVMCommands =
+            ''
+              /bin/nc -p 1234 -lke /bin/echo TESTVALUE
+            '';
+
+            network = {
+              enable = true;
+
+              # Work around udhcpc only getting a lease on eth0
+              postCommands = ''
+                /bin/ip addr add 192.168.1.2/24 dev eth1
+              '';
+
+              # Example configuration for OpenVPN
+              # This is the main reason for this test
+              openvpn = {
+                enable = true;
+                configuration = "${./initrd.ovpn}";
+              };
+            };
+          };
+        };
+
+      # VPN server and gateway for ovpnclient between vlan 1 and 2
+      ovpnserver =
+        { ... }:
+        {
+          virtualisation.vlans = [ 1 2 ];
+
+          # Enable NAT and forward port 12345 to port 1234
+          networking.nat = {
+            enable = true;
+            internalInterfaces = [ "tun0" ];
+            externalInterface = "eth2";
+            forwardPorts = [ { destination = "10.8.0.2:1234";
+                               sourcePort = 12345; } ];
+          };
+
+          # Trust tun0 and allow the VPN Server to be reached
+          networking.firewall = {
+            trustedInterfaces = [ "tun0" ];
+            allowedUDPPorts = [ 1194 ];
+          };
+
+          # Minimal OpenVPN server configuration
+          services.openvpn.servers.testserver =
+          {
+            config = ''
+              dev tun0
+              ifconfig 10.8.0.1 10.8.0.2
+              ${secretblock}
+            '';
+          };
+        };
+
+      # Client that resides in the "external" VLAN
+      testclient =
+        { ... }:
+        {
+          virtualisation.vlans = [ 2 ];
+        };
+  };
+
+
+  testScript =
+    ''
+      # Minimal test case, checks whether enabling (with invalid config) harms
+      # the boot process
+      with subtest("Check for successful boot with broken openvpn config"):
+          minimalboot.start()
+          # If we get to multi-user.target, we booted successfully
+          minimalboot.wait_for_unit("multi-user.target")
+          minimalboot.shutdown()
+
+      # Elaborated test case where the ovpnclient (where this module is used)
+      # can be reached by testclient only over ovpnserver.
+      # This is an indirect test for success.
+      with subtest("Check for connection from initrd VPN client, config as file"):
+          ovpnserver.start()
+          testclient.start()
+          ovpnclient.start()
+
+          # Wait until the OpenVPN Server is available
+          ovpnserver.wait_for_unit("openvpn-testserver.service")
+          ovpnserver.succeed("ping -c 1 10.8.0.1")
+
+          # Wait for the client to connect
+          ovpnserver.wait_until_succeeds("ping -c 1 10.8.0.2")
+
+          # Wait until the testclient has network
+          testclient.wait_for_unit("network.target")
+
+          # Check that ovpnclient is reachable over vlan 1
+          ovpnserver.succeed("nc -w 2 192.168.1.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable over tun0
+          ovpnserver.succeed("nc -w 2 10.8.0.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable from testclient over the gateway
+          testclient.succeed("nc -w 2 192.168.2.3 12345 | grep -q TESTVALUE")
+    '';
+})
diff --git a/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixos/tests/initrd-network-openvpn/initrd.ovpn
new file mode 100644
index 00000000000..5926a48af00
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/initrd.ovpn
@@ -0,0 +1,29 @@
+remote 192.168.1.3
+dev tun
+ifconfig 10.8.0.2 10.8.0.1
+# Only force VLAN 2 through the VPN
+route 192.168.2.0 255.255.255.0 10.8.0.1
+secret [inline]
+<secret>
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
+</secret>
\ No newline at end of file
diff --git a/nixos/tests/initrd-network-openvpn/shared.key b/nixos/tests/initrd-network-openvpn/shared.key
new file mode 100644
index 00000000000..248a91a3e3d
--- /dev/null
+++ b/nixos/tests/initrd-network-openvpn/shared.key
@@ -0,0 +1,21 @@
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index b6bdfea2277..889a00d4b56 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -50,6 +50,12 @@ let
 
             environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
 
+            # The installed tests need to be added to the test VM’s closure.
+            # Otherwise, their dependencies might not actually be registered
+            # as valid paths in the VM’s Nix store database,
+            # and `nix-store --query` commands run as part of the tests
+            # (for example when building Flatpak runtimes) will fail.
+            environment.variables.TESTED_PACKAGE_INSTALLED_TESTS = "${tested.installedTests}/share";
           };
 
           testScript =
diff --git a/nixos/tests/installed-tests/flatpak.nix b/nixos/tests/installed-tests/flatpak.nix
index 091c9932662..8aeeaca90f6 100644
--- a/nixos/tests/installed-tests/flatpak.nix
+++ b/nixos/tests/installed-tests/flatpak.nix
@@ -5,14 +5,11 @@ makeInstalledTest {
   withX11 = true;
 
   testConfig = {
-    services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
-    # common/x11.nix enables the auto display manager (lightdm)
-    services.xserver.displayManager.gdm.enable = false;
-    services.gnome3.core-utilities.enable = false;
+    xdg.portal.enable = true;
     services.flatpak.enable = true;
-    environment.systemPackages = with pkgs; [ gnupg ostree python2 ];
+    environment.systemPackages = with pkgs; [ gnupg ostree python3 ];
     virtualisation.memorySize = 2047;
-    virtualisation.diskSize = 1024;
+    virtualisation.diskSize = 3072;
   };
 
   testRunnerFlags = "--timeout 3600";
diff --git a/nixos/tests/installed-tests/ibus.nix b/nixos/tests/installed-tests/ibus.nix
index af54b612b50..a4bc2a7d7de 100644
--- a/nixos/tests/installed-tests/ibus.nix
+++ b/nixos/tests/installed-tests/ibus.nix
@@ -5,16 +5,12 @@ makeInstalledTest {
 
   testConfig = {
     i18n.inputMethod.enabled = "ibus";
+    systemd.user.services.ibus-daemon = {
+      serviceConfig.ExecStart = "${pkgs.ibus}/bin/ibus-daemon --xim --verbose";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+    };
   };
 
-  preTestScript = ''
-    # ibus has ibus-desktop-testing-runner but it tries to manage desktop session so we just spawn ibus-daemon ourselves
-    machine.succeed("ibus-daemon --daemonize --verbose")
-  '';
-
   withX11 = true;
-
-  # TODO: ibus-daemon is currently crashing or something
-  # maybe make ibus systemd service that auto-restarts?
-  meta.broken = true;
 }
diff --git a/nixos/tests/installed-tests/ostree.nix b/nixos/tests/installed-tests/ostree.nix
index eef7cace54c..90e09ad4ddf 100644
--- a/nixos/tests/installed-tests/ostree.nix
+++ b/nixos/tests/installed-tests/ostree.nix
@@ -3,21 +3,10 @@
 makeInstalledTest {
   tested = pkgs.ostree;
 
-  # TODO: Wrap/patch the tests directly in the package
   testConfig = {
     environment.systemPackages = with pkgs; [
-      (python3.withPackages (p: with p; [ pyyaml ]))
       gnupg
       ostree
     ];
-
-    # for GJS tests
-    environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [
-      gtk3
-      pango.out
-      ostree
-      gdk-pixbuf
-      atk
-    ]);
   };
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 8443b69359b..02b839fee3f 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -64,7 +64,7 @@ let
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
   testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
-                  , grubIdentifier, preBootCommands, extraConfig
+                  , grubIdentifier, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig
                   }:
     let iface = if grubVersion == 1 then "ide" else "virtio";
@@ -216,6 +216,7 @@ let
       machine = create_machine_named("boot-after-rebuild-switch")
       ${preBootCommands}
       machine.wait_for_unit("network.target")
+      ${postBootCommands}
       machine.shutdown()
 
       # Tests for validating clone configuration entries in grub menu
@@ -238,6 +239,7 @@ let
       with subtest("Set grub to boot the second configuration"):
           machine.succeed("grub-reboot 1")
 
+      ${postBootCommands}
       machine.shutdown()
 
       # Reboot Machine
@@ -252,12 +254,13 @@ let
       with subtest("We should find a file named /etc/gitconfig"):
           machine.succeed("test -e /etc/gitconfig")
 
+      ${postBootCommands}
       machine.shutdown()
     '';
 
 
   makeInstallerTest = name:
-    { createPartitions, preBootCommands ? "", extraConfig ? ""
+    { createPartitions, preBootCommands ? "", postBootCommands ? "", extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
     , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
@@ -335,7 +338,7 @@ let
       };
 
       testScript = testScriptFun {
-        inherit bootLoader createPartitions preBootCommands
+        inherit bootLoader createPartitions preBootCommands postBootCommands
                 grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
                 testSpecialisationConfig;
       };
@@ -552,16 +555,26 @@ in {
           + " mkpart primary 2048M -1s"  # PV2
           + " set 2 lvm on",
           "udevadm settle",
+          "sleep 1",
           "pvcreate /dev/vda1 /dev/vda2",
+          "sleep 1",
           "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
+          "sleep 1",
           "lvcreate --size 1G --name swap MyVolGroup",
-          "lvcreate --size 2G --name nixos MyVolGroup",
+          "sleep 1",
+          "lvcreate --size 3G --name nixos MyVolGroup",
+          "sleep 1",
           "mkswap -f /dev/MyVolGroup/swap -L swap",
           "swapon -L swap",
           "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
           "mount LABEL=nixos /mnt",
       )
     '';
+    postBootCommands = ''
+      assert "loaded active" in machine.succeed(
+          "systemctl list-units 'lvm2-pvscan@*' -ql --no-legend | tee /dev/stderr"
+      )
+    '';
   };
 
   # Boot off an encrypted root partition with the default LUKS header format
@@ -650,6 +663,32 @@ in {
     '';
   };
 
+  bcache = makeInstallerTest "bcache" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda --"
+          + " mklabel msdos"
+          + " mkpart primary ext2 1M 50MB"  # /boot
+          + " mkpart primary 50MB 512MB  "  # swap
+          + " mkpart primary 512MB 1024MB"  # Cache (typically SSD)
+          + " mkpart primary 1024MB -1s ",  # Backing device (typically HDD)
+          "modprobe bcache",
+          "udevadm settle",
+          "make-bcache -B /dev/vda4 -C /dev/vda3",
+          "echo /dev/vda3 > /sys/fs/bcache/register",
+          "echo /dev/vda4 > /sys/fs/bcache/register",
+          "udevadm settle",
+          "mkfs.ext3 -L nixos /dev/bcache0",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "mkswap -f /dev/vda2 -L swap",
+          "swapon -L swap",
+      )
+    '';
+  };
+
   # Test a basic install using GRUB 1.
   grub1 = makeInstallerTest "grub1" {
     createPartitions = ''
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/ipfs.nix
index 4d721aec0c7..9c0ff5306e0 100644
--- a/nixos/tests/ipfs.nix
+++ b/nixos/tests/ipfs.nix
@@ -7,19 +7,33 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   nodes.machine = { ... }: {
     services.ipfs = {
       enable = true;
+      # Also will add a unix domain socket socket API address, see module.
+      startWhenNeeded = true;
       apiAddress = "/ip4/127.0.0.1/tcp/2324";
     };
   };
 
   testScript = ''
     start_all()
-    machine.wait_for_unit("ipfs")
 
-    machine.wait_until_succeeds("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
+    # IPv4 activation
+
+    machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
     ipfs_hash = machine.succeed(
         "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | awk '{ print $2 }'"
     )
 
     machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
+
+    # Unix domain socket activation
+
+    machine.stop_job("ipfs")
+
+    ipfs_hash = machine.succeed(
+        "echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'"
+    )
+    machine.succeed(
+        f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
+    )
   '';
 })
diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
new file mode 100644
index 00000000000..42762dfdad8
--- /dev/null
+++ b/nixos/tests/jitsi-meet.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "jitsi-meet";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = teams.jitsi.members;
+  };
+
+  nodes = {
+    client = { nodes, pkgs, ... }: {
+    };
+    server = { config, pkgs, ... }: {
+      services.jitsi-meet = {
+        enable = true;
+        hostName = "server";
+      };
+      services.jitsi-videobridge.openFirewall = true;
+
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services.nginx.virtualHosts.server = {
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      security.acme.email = "me@example.org";
+      security.acme.acceptTerms = true;
+      security.acme.server = "https://example.com"; # self-signed only
+    };
+  };
+
+  testScript = ''
+    server.wait_for_unit("jitsi-videobridge2.service")
+    server.wait_for_unit("jicofo.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("prosody.service")
+
+    server.wait_until_succeeds(
+        "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u jicofo -o cat | grep -q 'connected .JID: focus@auth.server'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'focus.server:component: External component successfully authenticated'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.server'"
+    )
+
+    client.wait_for_unit("network.target")
+    assert "<title>Jitsi Meet</title>" in client.succeed("curl -sSfkL http://server/")
+  '';
+})
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index adb73650689..8cfac10b6dc 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/nixos/tests/kubernetes/base.nix
@@ -3,13 +3,12 @@
   pkgs ? import ../../.. { inherit system config; }
 }:
 
-with import ../../lib/testing.nix { inherit system pkgs; };
+with import ../../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
   mkKubernetesBaseTest =
     { name, domain ? "my.zyx", test, machines
-    , pkgs ? import <nixpkgs> { inherit system; }
     , extraConfiguration ? null }:
     let
       masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines));
@@ -75,10 +74,8 @@ let
       ) machines;
 
       testScript = ''
-        startAll;
-
-        ${test}
-      '';
+        start_all()
+      '' + test;
     };
 
   mkKubernetesMultiNodeTest = attrs: mkKubernetesBaseTest ({
diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix
index 638942e1540..890499a0fb8 100644
--- a/nixos/tests/kubernetes/dns.nix
+++ b/nixos/tests/kubernetes/dns.nix
@@ -75,51 +75,75 @@ let
   singleNodeTest = {
     test = ''
       # prepare machine1 for test
-      $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready");
-      $machine1->waitUntilSucceeds("docker load < ${redisImage}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
-      $machine1->waitUntilSucceeds("docker load < ${probeImage}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+      machine1.wait_until_succeeds("kubectl get node machine1.${domain} | grep -w Ready")
+      machine1.wait_until_succeeds(
+          "docker load < ${redisImage}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisPod}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisService}"
+      )
+      machine1.wait_until_succeeds(
+          "docker load < ${probeImage}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${probePod}"
+      )
 
       # check if pods are running
-      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
+      machine1.wait_until_succeeds("kubectl get pod redis | grep Running")
+      machine1.wait_until_succeeds("kubectl get pod probe | grep Running")
+      machine1.wait_until_succeeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'")
 
       # check dns on host (dnsmasq)
-      $machine1->succeed("host redis.default.svc.cluster.local");
+      machine1.succeed("host redis.default.svc.cluster.local")
 
       # check dns inside the container
-      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+      machine1.succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local")
     '';
   };
 
   multiNodeTest = {
     test = ''
       # Node token exchange
-      $machine1->waitUntilSucceeds("cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret");
-      $machine2->waitUntilSucceeds("cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join");
+      machine1.wait_until_succeeds(
+          "cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret"
+      )
+      machine2.wait_until_succeeds(
+          "cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join"
+      )
 
       # prepare machines for test
-      $machine1->waitUntilSucceeds("kubectl get node machine2.${domain} | grep -w Ready");
-      $machine2->waitUntilSucceeds("docker load < ${redisImage}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
-      $machine2->waitUntilSucceeds("docker load < ${probeImage}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+      machine1.wait_until_succeeds("kubectl get node machine2.${domain} | grep -w Ready")
+      machine2.wait_until_succeeds(
+          "docker load < ${redisImage}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisPod}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisService}"
+      )
+      machine2.wait_until_succeeds(
+          "docker load < ${probeImage}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${probePod}"
+      )
 
       # check if pods are running
-      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
+      machine1.wait_until_succeeds("kubectl get pod redis | grep Running")
+      machine1.wait_until_succeeds("kubectl get pod probe | grep Running")
+      machine1.wait_until_succeeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'")
 
       # check dns on hosts (dnsmasq)
-      $machine1->succeed("host redis.default.svc.cluster.local");
-      $machine2->succeed("host redis.default.svc.cluster.local");
+      machine1.succeed("host redis.default.svc.cluster.local")
+      machine2.succeed("host redis.default.svc.cluster.local")
 
       # check dns inside the container
-      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+      machine1.succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local")
     '';
   };
 in {
diff --git a/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix
index 3ce7adcd0d7..c922da515d9 100644
--- a/nixos/tests/kubernetes/rbac.nix
+++ b/nixos/tests/kubernetes/rbac.nix
@@ -94,43 +94,67 @@ let
 
   singlenode = base // {
     test = ''
-      $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready");
-
-      $machine1->waitUntilSucceeds("docker load < ${kubectlImage}");
-
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
-
-      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
-
-      $machine1->waitUntilSucceeds("kubectl exec -ti kubectl -- kubectl get pods");
-      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
-      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+      machine1.wait_until_succeeds("kubectl get node machine1.my.zyx | grep -w Ready")
+
+      machine1.wait_until_succeeds(
+          "docker load < ${kubectlImage}"
+      )
+
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roServiceAccount}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRole}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRoleBinding}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${kubectlPod}"
+      )
+
+      machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
+
+      machine1.wait_until_succeeds("kubectl exec -ti kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl")
     '';
   };
 
   multinode = base // {
     test = ''
       # Node token exchange
-      $machine1->waitUntilSucceeds("cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret");
-      $machine2->waitUntilSucceeds("cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join");
-
-      $machine1->waitUntilSucceeds("kubectl get node machine2.my.zyx | grep -w Ready");
-
-      $machine2->waitUntilSucceeds("docker load < ${kubectlImage}");
-
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
-      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
-      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
-
-      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
-
-      $machine1->waitUntilSucceeds("kubectl exec -ti kubectl -- kubectl get pods");
-      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
-      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+      machine1.wait_until_succeeds(
+          "cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret"
+      )
+      machine2.wait_until_succeeds(
+          "cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join"
+      )
+
+      machine1.wait_until_succeeds("kubectl get node machine2.my.zyx | grep -w Ready")
+
+      machine2.wait_until_succeeds(
+          "docker load < ${kubectlImage}"
+      )
+
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roServiceAccount}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRole}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRoleBinding}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${kubectlPod}"
+      )
+
+      machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
+
+      machine1.wait_until_succeeds("kubectl exec -ti kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl")
     '';
   };
 
diff --git a/nixos/tests/leaps.nix b/nixos/tests/leaps.nix
index 65b475d734e..ac0c602d445 100644
--- a/nixos/tests/leaps.nix
+++ b/nixos/tests/leaps.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ pkgs,  ... }:
   };
 
   nodes =
-    { 
+    {
       client = { };
 
       server =
diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index 198171082d8..c33c7503993 100644
--- a/nixos/tests/lorri/default.nix
+++ b/nixos/tests/lorri/default.nix
@@ -18,7 +18,7 @@ import ../make-test-python.nix {
     machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout")
 
     # Ping the daemon
-    machine.succeed("lorri internal__ping shell.nix")
+    machine.succeed("lorri internal ping shell.nix")
 
     # Wait for the daemon to finish the build
     machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stdout")
diff --git a/nixos/tests/lxd-nftables.nix b/nixos/tests/lxd-nftables.nix
new file mode 100644
index 00000000000..25517914db8
--- /dev/null
+++ b/nixos/tests/lxd-nftables.nix
@@ -0,0 +1,50 @@
+# This test makes sure that lxd stops implicitly depending on iptables when
+# user enabled nftables.
+#
+# It has been extracted from `lxd.nix` for clarity, and because switching from
+# iptables to nftables requires a full reboot, which is a bit hard inside NixOS
+# tests.
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "lxd-nftables";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ patryk27 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      lxd.enable = true;
+    };
+
+    networking = {
+      firewall.enable = false;
+      nftables.enable = true;
+      nftables.ruleset = ''
+        table inet filter {
+          chain incoming {
+            type filter hook input priority 0;
+            policy accept;
+          }
+
+          chain forward {
+            type filter hook forward priority 0;
+            policy accept;
+          }
+
+          chain output {
+            type filter hook output priority 0;
+            policy accept;
+          }
+        }
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("network.target")
+
+    with subtest("When nftables are enabled, lxd doesn't depend on iptables anymore"):
+        machine.succeed("lsmod | grep nf_tables")
+        machine.fail("lsmod | grep ip_tables")
+  '';
+})
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
new file mode 100644
index 00000000000..db2d44dff55
--- /dev/null
+++ b/nixos/tests/lxd.nix
@@ -0,0 +1,135 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  # Since we don't have access to the internet during the tests, we have to
+  # pre-fetch lxd containers beforehand.
+  #
+  # I've chosen to import Alpine Linux, because its image is turbo-tiny and,
+  # generally, sufficient for our tests.
+
+  alpine-meta = pkgs.fetchurl {
+    url = "https://uk.images.linuxcontainers.org/images/alpine/3.11/i386/default/20200608_13:00/lxd.tar.xz";
+    sha256 = "1hkvaj3rr333zmx1759njy435lps33gl4ks8zfm7m4nqvipm26a0";
+  };
+
+  alpine-rootfs = pkgs.fetchurl {
+    url = "https://uk.images.linuxcontainers.org/images/alpine/3.11/i386/default/20200608_13:00/rootfs.tar.xz";
+    sha256 = "1v82zdra4j5xwsff09qlp7h5vbsg54s0j7rdg4rynichfid3r347";
+  };
+
+  lxd-config = pkgs.writeText "config.yaml" ''
+    storage_pools:
+      - name: default
+        driver: dir
+        config:
+          source: /var/lxd-pool
+
+    networks:
+      - name: lxdbr0
+        type: bridge
+        config:
+          ipv4.address: auto
+          ipv6.address: none
+
+    profiles:
+      - name: default
+        devices:
+          eth0:
+            name: eth0
+            network: lxdbr0
+            type: nic
+          root:
+            path: /
+            pool: default
+            type: disk
+  '';
+
+in {
+  name = "lxd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ patryk27 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      # Since we're testing `limits.cpu`, we've gotta have a known number of
+      # cores to lay on
+      cores = 2;
+
+      # Ditto, for `limits.memory`
+      memorySize = 512;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+
+    # It takes additional second for lxd to settle
+    machine.sleep(1)
+
+    # lxd expects the pool's directory to already exist
+    machine.succeed("mkdir /var/lxd-pool")
+
+    machine.succeed(
+        "cat ${lxd-config} | lxd init --preseed"
+    )
+
+    machine.succeed(
+        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+    )
+
+    with subtest("Containers can be launched and destroyed"):
+        machine.succeed("lxc launch alpine test")
+        machine.succeed("lxc exec test true")
+        machine.succeed("lxc delete -f test")
+
+    with subtest("Containers are being mounted with lxcfs inside"):
+        machine.succeed("lxc launch alpine test")
+
+        ## ---------- ##
+        ## limits.cpu ##
+
+        machine.succeed("lxc config set test limits.cpu 1")
+
+        # Since Alpine doesn't have `nproc` pre-installed, we've gotta resort
+        # to the primal methods
+        assert (
+            "1"
+            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        machine.succeed("lxc config set test limits.cpu 2")
+
+        assert (
+            "2"
+            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        ## ------------- ##
+        ## limits.memory ##
+
+        machine.succeed("lxc config set test limits.memory 64MB")
+
+        assert (
+            "MemTotal:          62500 kB"
+            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc config set test limits.memory 128MB")
+
+        assert (
+            "MemTotal:         125000 kB"
+            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc delete -f test")
+
+    with subtest("Unless explicitly changed, lxd leans on iptables"):
+        machine.succeed("lsmod | grep ip_tables")
+        machine.fail("lsmod | grep nf_tables")
+  '';
+})
diff --git a/nixos/tests/mathics.nix b/nixos/tests/mathics.nix
deleted file mode 100644
index fcbeeb18a72..00000000000
--- a/nixos/tests/mathics.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-import ./make-test.nix ({ pkgs, ... }: {
-  name = "mathics";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ benley ];
-  };
-
-  nodes = {
-    machine = { ... }: {
-      services.mathics.enable = true;
-      services.mathics.port = 8888;
-    };
-  };
-
-  testScript = ''
-    startAll;
-    $machine->waitForUnit("mathics.service");
-    $machine->waitForOpenPort(8888);
-    $machine->succeed("curl http://localhost:8888/");
-  '';
-})
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index f3623aa3c09..9ca80872176 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -29,8 +29,8 @@ import ./make-test-python.nix ({ pkgs, ... } : let
 in {
 
   name = "matrix-synapse";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ corngood ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = teams.matrix.members;
   };
 
   nodes = {
diff --git a/nixos/tests/mesos.nix b/nixos/tests/mesos.nix
deleted file mode 100644
index 2e6dc0eda06..00000000000
--- a/nixos/tests/mesos.nix
+++ /dev/null
@@ -1,92 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : rec {
-  name = "mesos";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ offline kamilchm cstrahan ];
-  };
-
-  nodes = {
-    master = { ... }: {
-      networking.firewall.enable = false;
-      services.zookeeper.enable = true;
-      services.mesos.master = {
-          enable = true;
-          zk = "zk://master:2181/mesos";
-      };
-    };
-
-    slave = { ... }: {
-      networking.firewall.enable = false;
-      networking.nat.enable = true;
-      virtualisation.docker.enable = true;
-      services.mesos = {
-        slave = {
-          enable = true;
-          master = "master:5050";
-          dockerRegistry = registry;
-          executorEnvironmentVariables = {
-            PATH = "/run/current-system/sw/bin";
-          };
-        };
-      };
-    };
-  };
-
-  simpleDocker = pkgs.dockerTools.buildImage {
-    name = "echo";
-    tag = "latest";
-    contents = [ pkgs.stdenv.shellPackage pkgs.coreutils ];
-    config = {
-      Env = [
-        # When shell=true, mesos invokes "sh -c '<cmd>'", so make sure "sh" is
-        # on the PATH.
-        "PATH=${pkgs.stdenv.shellPackage}/bin:${pkgs.coreutils}/bin"
-      ];
-      Entrypoint = [ "echo" ];
-    };
-  };
-
-  registry = pkgs.runCommand "registry" { } ''
-    mkdir -p $out
-    cp ${simpleDocker} $out/echo:latest.tar
-  '';
-
-  testFramework = pkgs.pythonPackages.buildPythonPackage {
-    name = "mesos-tests";
-    propagatedBuildInputs = [ pkgs.mesos ];
-    catchConflicts = false;
-    src = ./mesos_test.py;
-    phases = [ "installPhase" "fixupPhase" ];
-    installPhase = ''
-      install -Dvm 0755 $src $out/bin/mesos_test.py
-
-      echo "done" > test.result
-      tar czf $out/test.tar.gz test.result
-    '';
-  };
-
-  testScript =
-    ''
-      startAll;
-      $master->waitForUnit("zookeeper.service");
-      $master->waitForUnit("mesos-master.service");
-      $slave->waitForUnit("docker.service");
-      $slave->waitForUnit("mesos-slave.service");
-      $master->waitForOpenPort(2181);
-      $master->waitForOpenPort(5050);
-      $slave->waitForOpenPort(5051);
-
-      # is slave registered?
-      $master->waitUntilSucceeds("curl -s --fail http://master:5050/master/slaves".
-                                 " | grep -q \"\\\"hostname\\\":\\\"slave\\\"\"");
-
-      # try to run docker image
-      $master->succeed("${pkgs.mesos}/bin/mesos-execute --master=master:5050".
-                       " --resources=\"cpus:0.1;mem:32\" --name=simple-docker".
-                       " --containerizer=mesos --docker_image=echo:latest".
-                       " --shell=true --command=\"echo done\" | grep -q TASK_FINISHED");
-
-      # simple command with .tar.gz uri
-      $master->succeed("${testFramework}/bin/mesos_test.py master ".
-                       "${testFramework}/test.tar.gz");
-    '';
-})
diff --git a/nixos/tests/mesos_test.py b/nixos/tests/mesos_test.py
deleted file mode 100644
index be8bb32e49a..00000000000
--- a/nixos/tests/mesos_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python
-import uuid
-import time
-import subprocess
-import os
-
-import sys
-
-from mesos.interface import Scheduler
-from mesos.native import MesosSchedulerDriver
-from mesos.interface import mesos_pb2
-
-def log(msg):
-    process = subprocess.Popen("systemd-cat", stdin=subprocess.PIPE)
-    (out,err) = process.communicate(msg)
-
-class NixosTestScheduler(Scheduler):
-    def __init__(self):
-        self.master_ip = sys.argv[1]
-        self.download_uri = sys.argv[2]
-
-    def resourceOffers(self, driver, offers):
-        log("XXX got resource offer")
-
-        offer = offers[0]
-        task = self.new_task(offer)
-        uri = task.command.uris.add()
-        uri.value = self.download_uri
-        task.command.value = "cat test.result"
-        driver.launchTasks(offer.id, [task])
-
-    def statusUpdate(self, driver, update):
-        log("XXX status update")
-        if update.state == mesos_pb2.TASK_FAILED:
-            log("XXX test task failed with message: " + update.message)
-            driver.stop()
-            sys.exit(1)
-        elif update.state == mesos_pb2.TASK_FINISHED:
-            driver.stop()
-            sys.exit(0)
-
-    def new_task(self, offer):
-        task = mesos_pb2.TaskInfo()
-        id = uuid.uuid4()
-        task.task_id.value = str(id)
-        task.slave_id.value = offer.slave_id.value
-        task.name = "task {}".format(str(id))
-
-        cpus = task.resources.add()
-        cpus.name = "cpus"
-        cpus.type = mesos_pb2.Value.SCALAR
-        cpus.scalar.value = 0.1
-
-        mem = task.resources.add()
-        mem.name = "mem"
-        mem.type = mesos_pb2.Value.SCALAR
-        mem.scalar.value = 32
-
-        return task
-
-if __name__ == '__main__':
-    log("XXX framework started")
-
-    framework = mesos_pb2.FrameworkInfo()
-    framework.user = "root"
-    framework.name = "nixos-test-framework"
-    driver = MesosSchedulerDriver(
-        NixosTestScheduler(),
-        framework,
-        sys.argv[1] + ":5050"
-    )
-    driver.run()
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 17260ce6406..ae150553273 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -20,12 +20,24 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
         { fsType = "tmpfs";
           options = [ "mode=1777" "noauto" ];
         };
+        # Tests https://discourse.nixos.org/t/how-to-make-a-derivations-executables-have-the-s-permission/8555
+        "/user-mount/point" = {
+          device = "/user-mount/source";
+          fsType = "none";
+          options = [ "bind" "rw" "user" "noauto" ];
+        };
+        "/user-mount/denied-point" = {
+          device = "/user-mount/denied-source";
+          fsType = "none";
+          options = [ "bind" "rw" "noauto" ];
+        };
       };
       systemd.automounts = singleton
         { wantedBy = [ "multi-user.target" ];
           where = "/tmp2";
         };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      users.users.alice = { isNormalUser = true; };
       security.sudo = { enable = true; wheelNeedsPassword = false; };
       boot.kernel.sysctl."vm.swappiness" = 1;
       boot.kernelParams = [ "vsyscall=emulate" ];
@@ -112,6 +124,26 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
           machine.succeed("touch /tmp2/x")
           machine.succeed("grep '/tmp2 tmpfs' /proc/mounts")
 
+      with subtest(
+          "Whether mounting by a user is possible with the `user` option in fstab (#95444)"
+      ):
+          machine.succeed("mkdir -p /user-mount/source")
+          machine.succeed("touch /user-mount/source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/source")
+          machine.succeed("mkdir /user-mount/point")
+          machine.succeed("chown alice:users /user-mount/point")
+          machine.succeed("su - alice -c 'mount /user-mount/point'")
+          machine.succeed("su - alice -c 'ls /user-mount/point/file'")
+      with subtest(
+          "Whether mounting by a user is denied without the `user` option in  fstab"
+      ):
+          machine.succeed("mkdir -p /user-mount/denied-source")
+          machine.succeed("touch /user-mount/denied-source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/denied-source")
+          machine.succeed("mkdir /user-mount/denied-point")
+          machine.succeed("chown alice:users /user-mount/denied-point")
+          machine.fail("su - alice -c 'mount /user-mount/denied-point'")
+
       with subtest("shell-vars"):
           machine.succeed('[ -n "$NIX_PATH" ]')
 
diff --git a/nixos/tests/molly-brown.nix b/nixos/tests/molly-brown.nix
new file mode 100644
index 00000000000..09ce42726ca
--- /dev/null
+++ b/nixos/tests/molly-brown.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let testString = "NixOS Gemini test successful";
+  in {
+
+    name = "molly-brown";
+    meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ ehmry ]; };
+
+    nodes = {
+
+      geminiServer = { config, pkgs, ... }:
+        let
+          inherit (config.networking) hostName;
+          cfg = config.services.molly-brown;
+        in {
+
+          environment.systemPackages = [
+            (pkgs.writeScriptBin "test-gemini" ''
+              #!${pkgs.python3}/bin/python
+
+              import socket
+              import ssl
+              import tempfile
+              import textwrap
+              import urllib.parse
+
+              url = "gemini://geminiServer/init.gmi"
+              parsed_url = urllib.parse.urlparse(url)
+
+              s = socket.create_connection((parsed_url.netloc, 1965))
+              context = ssl.SSLContext()
+              context.check_hostname = False
+              context.verify_mode = ssl.CERT_NONE
+              s = context.wrap_socket(s, server_hostname=parsed_url.netloc)
+              s.sendall((url + "\r\n").encode("UTF-8"))
+              fp = s.makefile("rb")
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+            '')
+          ];
+
+          networking.firewall.allowedTCPPorts = [ cfg.settings.Port ];
+
+          services.molly-brown = {
+            enable = true;
+            docBase = "/tmp/docs";
+            certPath = "/tmp/cert.pem";
+            keyPath = "/tmp/key.pem";
+          };
+
+          systemd.services.molly-brown.preStart = ''
+            ${pkgs.openssl}/bin/openssl genrsa -out "/tmp/key.pem"
+            ${pkgs.openssl}/bin/openssl req -new \
+              -subj "/CN=${config.networking.hostName}" \
+              -key "/tmp/key.pem" -out /tmp/request.pem
+            ${pkgs.openssl}/bin/openssl x509 -req -days 3650 \
+              -in /tmp/request.pem -signkey "/tmp/key.pem" -out "/tmp/cert.pem"
+
+            mkdir -p "${cfg.settings.DocBase}"
+            echo "${testString}" > "${cfg.settings.DocBase}/test.gmi"
+          '';
+        };
+    };
+    testScript = ''
+      geminiServer.wait_for_unit("molly-brown")
+      geminiServer.wait_for_open_port(1965)
+      geminiServer.succeed("test-gemini")
+    '';
+
+  })
diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix
index a637ec4bfc0..1a712388301 100644
--- a/nixos/tests/mongodb.nix
+++ b/nixos/tests/mongodb.nix
@@ -15,7 +15,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       node.wait_for_open_port(27017)
 
       assert "hello" in node.succeed(
-          "mongo ${testQuery}"
+          "${pkg}/bin/mongo ${testQuery}"
       )
 
       node.execute(
@@ -36,6 +36,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
           mongodb-3_4
           mongodb-3_6
           mongodb-4_0
+          mongodb-4_2
         ];
       };
     };
@@ -44,8 +45,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
       node.start()
     ''
       + runMongoDBTest pkgs.mongodb-3_4
-      + runMongoDBTest pkgs.mongodb-3_6 
+      + runMongoDBTest pkgs.mongodb-3_6
       + runMongoDBTest pkgs.mongodb-4_0
+      + runMongoDBTest pkgs.mongodb-4_2
       + ''
         node.shutdown()
       '';
diff --git a/nixos/tests/mysql/mariadb-galera-mariabackup.nix b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
index 73abf6c555f..cae55878060 100644
--- a/nixos/tests/mysql/mariadb-galera-mariabackup.nix
+++ b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
@@ -55,9 +55,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://";
             wsrep_cluster_name = "galera";
             wsrep_node_address = "192.168.1.1";
@@ -102,9 +102,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://galera_01,galera_02,galera_03";
             wsrep_cluster_name = "galera";
             wsrep_node_address = "192.168.1.2";
@@ -149,9 +149,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://galera_01,galera_02,galera_03";
             wsrep_cluster_name = "galera";
             wsrep_node_address = "192.168.1.3";
@@ -184,17 +184,17 @@ in {
     galera_03.wait_for_unit("mysql")
     galera_03.wait_for_open_port(3306)
     galera_02.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db1;' -N | grep 37"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37"
     )
     galera_02.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
     )
     galera_02.succeed("systemctl stop mysql")
     galera_01.succeed(
         "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (38);'"
     )
     galera_03.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
     )
     galera_01.succeed(
         "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (39);'"
@@ -202,22 +202,22 @@ in {
     galera_02.succeed("systemctl start mysql")
     galera_02.wait_for_open_port(3306)
     galera_02.succeed(
-        "sudo -u testuser mysql -u root -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
+        "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
     )
     galera_03.succeed(
-        "sudo -u testuser mysql -u root -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
+        "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
     )
     galera_01.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db3;' -N | grep 39"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 39"
     )
     galera_02.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db2;' -N | grep 38"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 38"
     )
     galera_03.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db1;' -N | grep 37"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37"
     )
     galera_01.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'")
-    galera_02.succeed("sudo -u testuser mysql -u root -e 'use testdb; drop table db2;'")
-    galera_03.succeed("sudo -u testuser mysql -u root -e 'use testdb; drop table db1;'")
+    galera_02.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'")
+    galera_03.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'")
   '';
 })
diff --git a/nixos/tests/mysql/mariadb-galera-rsync.nix b/nixos/tests/mysql/mariadb-galera-rsync.nix
index cacae4569b5..4318efae8a9 100644
--- a/nixos/tests/mysql/mariadb-galera-rsync.nix
+++ b/nixos/tests/mysql/mariadb-galera-rsync.nix
@@ -51,9 +51,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://";
             wsrep_cluster_name = "galera-rsync";
             wsrep_node_address = "192.168.2.1";
@@ -97,9 +97,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://galera_04,galera_05,galera_06";
             wsrep_cluster_name = "galera-rsync";
             wsrep_node_address = "192.168.2.2";
@@ -143,9 +143,9 @@ in {
           };
           galera = {
             wsrep_on = "ON";
-            wsrep_debug = "OFF";
+            wsrep_debug = "NONE";
             wsrep_retry_autocommit = "3";
-            wsrep_provider = "${pkgs.mariadb-galera_25}/lib/galera/libgalera_smm.so";
+            wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so";
             wsrep_cluster_address = "gcomm://galera_04,galera_05,galera_06";
             wsrep_cluster_name = "galera-rsync";
             wsrep_node_address = "192.168.2.3";
@@ -177,17 +177,17 @@ in {
     galera_06.wait_for_unit("mysql")
     galera_06.wait_for_open_port(3306)
     galera_05.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db1;' -N | grep 41"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41"
     )
     galera_05.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
     )
     galera_05.succeed("systemctl stop mysql")
     galera_04.succeed(
         "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (42);'"
     )
     galera_06.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
     )
     galera_04.succeed(
         "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (43);'"
@@ -195,22 +195,22 @@ in {
     galera_05.succeed("systemctl start mysql")
     galera_05.wait_for_open_port(3306)
     galera_05.succeed(
-        "sudo -u testuser mysql -u root -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
+        "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
     )
     galera_06.succeed(
-        "sudo -u testuser mysql -u root -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
+        "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
     )
     galera_04.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db3;' -N | grep 43"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 43"
     )
     galera_05.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db2;' -N | grep 42"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 42"
     )
     galera_06.succeed(
-        "sudo -u testuser mysql -u root -e 'use testdb; select test_id from db1;' -N | grep 41"
+        "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41"
     )
     galera_04.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'")
-    galera_05.succeed("sudo -u testuser mysql -u root -e 'use testdb; drop table db2;'")
-    galera_06.succeed("sudo -u testuser mysql -u root -e 'use testdb; drop table db1;'")
+    galera_05.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'")
+    galera_06.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'")
   '';
 })
diff --git a/nixos/tests/mysql/mysql-replication.nix b/nixos/tests/mysql/mysql-replication.nix
index 81038dccd94..b5e00325019 100644
--- a/nixos/tests/mysql/mysql-replication.nix
+++ b/nixos/tests/mysql/mysql-replication.nix
@@ -59,7 +59,7 @@ in
     master.wait_for_open_port(3306)
     # Wait for testdb to be fully populated (5 rows).
     master.wait_until_succeeds(
-        "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+        "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
     )
 
     slave1.start()
@@ -71,19 +71,21 @@ in
 
     # wait for replications to finish
     slave1.wait_until_succeeds(
-        "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+        "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
     )
     slave2.wait_until_succeeds(
-        "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+        "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
     )
 
     slave2.succeed("systemctl stop mysql")
-    master.succeed("echo 'insert into testdb.tests values (123, 456);' | mysql -u root -N")
+    master.succeed(
+        "echo 'insert into testdb.tests values (123, 456);' | sudo -u mysql mysql -u mysql -N"
+    )
     slave2.succeed("systemctl start mysql")
     slave2.wait_for_unit("mysql")
     slave2.wait_for_open_port(3306)
     slave2.wait_until_succeeds(
-        "echo 'select * from testdb.tests where Id = 123;' | mysql -u root -N | grep 456"
+        "echo 'select * from testdb.tests where Id = 123;' | sudo -u mysql mysql -u mysql -N | grep 456"
     )
   '';
 })
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index d236ce94632..5437a286043 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -5,20 +5,34 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
   };
 
   nodes = {
-    mysql =
+    mysql57 =
       { pkgs, ... }:
 
       {
+        users.users.testuser = { };
+        users.users.testuser2 = { };
         services.mysql.enable = true;
         services.mysql.initialDatabases = [
-          { name = "testdb"; schema = ./testdb.sql; }
-          { name = "empty_testdb"; }
+          { name = "testdb3"; schema = ./testdb.sql; }
         ];
         # note that using pkgs.writeText here is generally not a good idea,
         # as it will store the password in world-readable /nix/store ;)
         services.mysql.initialScript = pkgs.writeText "mysql-init.sql" ''
-          CREATE USER 'passworduser'@'localhost' IDENTIFIED BY 'password123';
+          CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure';
+          GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost';
         '';
+        services.mysql.ensureDatabases = [ "testdb" "testdb2" ];
+        services.mysql.ensureUsers = [{
+          name = "testuser";
+          ensurePermissions = {
+            "testdb.*" = "ALL PRIVILEGES";
+          };
+        } {
+          name = "testuser2";
+          ensurePermissions = {
+            "testdb2.*" = "ALL PRIVILEGES";
+          };
+        }];
         services.mysql.package = pkgs.mysql57;
       };
 
@@ -30,16 +44,30 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
         # Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
         virtualisation.memorySize = 1024;
 
+        users.users.testuser = { };
+        users.users.testuser2 = { };
         services.mysql.enable = true;
         services.mysql.initialDatabases = [
-          { name = "testdb"; schema = ./testdb.sql; }
-          { name = "empty_testdb"; }
+          { name = "testdb3"; schema = ./testdb.sql; }
         ];
         # note that using pkgs.writeText here is generally not a good idea,
         # as it will store the password in world-readable /nix/store ;)
         services.mysql.initialScript = pkgs.writeText "mysql-init.sql" ''
-          CREATE USER 'passworduser'@'localhost' IDENTIFIED BY 'password123';
+          CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure';
+          GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost';
         '';
+        services.mysql.ensureDatabases = [ "testdb" "testdb2" ];
+        services.mysql.ensureUsers = [{
+          name = "testuser";
+          ensurePermissions = {
+            "testdb.*" = "ALL PRIVILEGES";
+          };
+        } {
+          name = "testuser2";
+          ensurePermissions = {
+            "testdb2.*" = "ALL PRIVILEGES";
+          };
+        }];
         services.mysql.package = pkgs.mysql80;
       };
 
@@ -81,17 +109,49 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
   testScript = ''
     start_all()
 
-    mysql.wait_for_unit("mysql")
-    mysql.succeed("echo 'use empty_testdb;' | mysql -u root")
-    mysql.succeed("echo 'use testdb; select * from tests;' | mysql -u root -N | grep 4")
-    # ';' acts as no-op, just check whether login succeeds with the user created from the initialScript
-    mysql.succeed("echo ';' | mysql -u passworduser --password=password123")
+    mysql57.wait_for_unit("mysql")
+    mysql57.succeed(
+        "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser"
+    )
+    mysql57.succeed(
+        "echo 'use testdb; insert into tests values (41);' | sudo -u testuser mysql -u testuser"
+    )
+    # Ensure testuser2 is not able to insert into testdb as mysql testuser2
+    mysql57.fail(
+        "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser2"
+    )
+    # Ensure testuser2 is not able to authenticate as mysql testuser
+    mysql57.fail(
+        "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser"
+    )
+    mysql57.succeed(
+        "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 41"
+    )
+    mysql57.succeed(
+        "echo 'use testdb3; select * from tests;' | mysql -u testuser3 --password=secure -N | grep 4"
+    )
 
     mysql80.wait_for_unit("mysql")
-    mysql80.succeed("echo 'use empty_testdb;' | mysql -u root")
-    mysql80.succeed("echo 'use testdb; select * from tests;' | mysql -u root -N | grep 4")
-    # ';' acts as no-op, just check whether login succeeds with the user created from the initialScript
-    mysql80.succeed("echo ';' | mysql -u passworduser --password=password123")
+    mysql80.succeed(
+        "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser"
+    )
+    mysql80.succeed(
+        "echo 'use testdb; insert into tests values (41);' | sudo -u testuser mysql -u testuser"
+    )
+    # Ensure testuser2 is not able to insert into testdb as mysql testuser2
+    mysql80.fail(
+        "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser2"
+    )
+    # Ensure testuser2 is not able to authenticate as mysql testuser
+    mysql80.fail(
+        "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser"
+    )
+    mysql80.succeed(
+        "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 41"
+    )
+    mysql80.succeed(
+        "echo 'use testdb3; select * from tests;' | mysql -u testuser3 --password=secure -N | grep 4"
+    )
 
     mariadb.wait_for_unit("mysql")
     mariadb.succeed(
@@ -112,32 +172,32 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
         "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42"
     )
 
-    # Check if TokuDB plugin works
+    # Check if RocksDB plugin works
     mariadb.succeed(
-        "echo 'use testdb; create table tokudb (test_id INT, PRIMARY KEY (test_id)) ENGINE = TokuDB;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; insert into tokudb values (25);' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; select test_id from tokudb;' | sudo -u testuser mysql -u testuser -N | grep 25"
+        "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28"
     )
     mariadb.succeed(
-        "echo 'use testdb; drop table tokudb;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser"
     )
-
-    # Check if RocksDB plugin works
+  '' + pkgs.stdenv.lib.optionalString pkgs.stdenv.isx86_64 ''
+    # Check if TokuDB plugin works
     mariadb.succeed(
-        "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; create table tokudb (test_id INT, PRIMARY KEY (test_id)) ENGINE = TokuDB;' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; insert into tokudb values (25);' | sudo -u testuser mysql -u testuser"
     )
     mariadb.succeed(
-        "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28"
+        "echo 'use testdb; select test_id from tokudb;' | sudo -u testuser mysql -u testuser -N | grep 25"
     )
     mariadb.succeed(
-        "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser"
+        "echo 'use testdb; drop table tokudb;' | sudo -u testuser mysql -u testuser"
     )
   '';
 })
diff --git a/nixos/tests/ncdns.nix b/nixos/tests/ncdns.nix
new file mode 100644
index 00000000000..507e20fe7cc
--- /dev/null
+++ b/nixos/tests/ncdns.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  fakeReply = pkgs.writeText "namecoin-reply.json" ''
+  { "error": null,
+    "id": 1,
+    "result": {
+      "address": "T31q8ucJ4dI1xzhxQ5QispfECld5c7Xw",
+      "expired": false,
+      "expires_in": 2248,
+      "height": 438155,
+      "name": "d/test",
+      "txid": "db61c0b2540ba0c1a2c8cc92af703a37002e7566ecea4dbf8727c7191421edfb",
+      "value": "{\"ip\": \"1.2.3.4\", \"email\": \"root@test.bit\",\"info\": \"Fake record\"}",
+      "vout": 0
+    }
+  }
+  '';
+in
+
+{
+  name = "ncdns";
+
+  nodes.server = { ... }: {
+    networking.nameservers = [ "127.0.0.1" ];
+
+    services.namecoind.rpc = {
+      address = "127.0.0.1";
+      user = "namecoin";
+      password = "secret";
+      port = 8332;
+    };
+
+    # Fake namecoin RPC server because we can't
+    # run a full node in a test.
+    systemd.services.namecoind = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        while true; do
+          echo -e "HTTP/1.1 200 OK\n\n $(<${fakeReply})\n" \
+            | ${pkgs.netcat}/bin/nc -N -l 127.0.0.1 8332
+        done
+      '';
+    };
+
+    services.ncdns = {
+      enable = true;
+      dnssec.enable = true;
+    };
+
+    services.pdns-recursor = {
+      enable = true;
+      dns.allowFrom = [ "127.0.0.0/8" ];
+      settings.loglevel = 8;
+      resolveNamecoin = true;
+    };
+
+    environment.systemPackages = [ pkgs.dnsutils ];
+
+  };
+
+  testScript = ''
+    with subtest("DNSSEC keys have been generated"):
+        server.wait_for_unit("ncdns")
+        server.wait_for_file("/var/lib/ncdns/bit.key")
+        server.wait_for_file("/var/lib/ncdns/bit-zone.key")
+
+    with subtest("DNSKEY bit record is present"):
+        server.wait_for_unit("pdns-recursor")
+        server.wait_for_open_port("53")
+        server.succeed("host -t DNSKEY bit")
+
+    with subtest("can resolve a .bit name"):
+        server.wait_for_unit("namecoind")
+        server.wait_for_open_port("8332")
+        assert "1.2.3.4" in server.succeed("host -t A test.bit")
+  '';
+})
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 3d8ab761a44..83d4f6465b6 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -8,7 +8,9 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
-  router = { config, pkgs, ... }:
+  qemu-flags = import ../lib/qemu-flags.nix { inherit pkgs; };
+
+  router = { config, pkgs, lib, ... }:
     with pkgs.lib;
     let
       vlanIfs = range 1 (length config.virtualisation.vlans);
@@ -30,17 +32,20 @@ let
       services.dhcpd4 = {
         enable = true;
         interfaces = map (n: "eth${toString n}") vlanIfs;
-        extraConfig = ''
-          authoritative;
-        '' + flip concatMapStrings vlanIfs (n: ''
+        extraConfig = flip concatMapStrings vlanIfs (n: ''
           subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
             option routers 192.168.${toString n}.1;
-            # XXX: technically it's _not guaranteed_ that IP addresses will be
-            # issued from the first item in range onwards! We assume that in
-            # our tests however.
             range 192.168.${toString n}.2 192.168.${toString n}.254;
           }
-        '');
+        '')
+        ;
+        machines = flip map vlanIfs (vlan:
+          {
+            hostName = "client${toString vlan}";
+            ethernetAddress = qemu-flags.qemuNicMac vlan 1;
+            ipAddress = "192.168.${toString vlan}.2";
+          }
+        );
       };
       services.radvd = {
         enable = true;
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index 92ac5c46e8f..72fb020dca7 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -9,14 +9,30 @@ in {
 
   nodes = {
     # The only thing the client needs to do is download a file.
-    client = { ... }: {};
+    client = { ... }: {
+      services.davfs2.enable = true;
+      system.activationScripts.davfs2-secrets = ''
+        echo "http://nextcloud/remote.php/webdav/ ${adminuser} ${adminpass}" > /tmp/davfs2-secrets
+        chmod 600 /tmp/davfs2-secrets
+      '';
+      fileSystems = pkgs.lib.mkVMOverride {
+        "/mnt/dav" = {
+          device = "http://nextcloud/remote.php/webdav/";
+          fsType = "davfs";
+          options = let
+            davfs2Conf = (pkgs.writeText "davfs2.conf" "secrets /tmp/davfs2-secrets");
+          in [ "conf=${davfs2Conf}" "x-systemd.automount" "noauto"];
+        };
+      };
+    };
 
-    nextcloud = { config, pkgs, ... }: {
+    nextcloud = { config, pkgs, ... }: let
+      cfg = config;
+    in {
       networking.firewall.allowedTCPPorts = [ 80 ];
 
       services.nextcloud = {
         enable = true;
-        nginx.enable = true;
         hostName = "nextcloud";
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
@@ -27,6 +43,8 @@ in {
           startAt = "20:00";
         };
       };
+
+      environment.systemPackages = [ cfg.services.nextcloud.occ ];
     };
   };
 
@@ -52,6 +70,8 @@ in {
   in ''
     start_all()
     nextcloud.wait_for_unit("multi-user.target")
+    # This is just to ensure the nextcloud-occ program is working
+    nextcloud.succeed("nextcloud-occ status")
     nextcloud.succeed("curl -sSf http://nextcloud/login")
     nextcloud.succeed(
         "${withRcloneEnv} ${copySharedFile}"
@@ -60,5 +80,6 @@ in {
     client.succeed(
         "${withRcloneEnv} ${diffSharedFile}"
     )
+    assert "hi" in client.succeed("cat /mnt/dav/test-shared-file")
   '';
 })
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
index 8db630be893..bec3815a3e1 100644
--- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -17,7 +17,6 @@ in {
       services.nextcloud = {
         enable = true;
         hostName = "nextcloud";
-        nginx.enable = true;
         https = true;
         caching = {
           apcu = true;
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 95219cac9be..40a208115c3 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -17,7 +17,6 @@ in {
       services.nextcloud = {
         enable = true;
         hostName = "nextcloud";
-        nginx.enable = true;
         caching = {
           apcu = false;
           redis = true;
diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix
index 1f2d0d453ea..078f0b7814c 100644
--- a/nixos/tests/nfs/kerberos.nix
+++ b/nixos/tests/nfs/kerberos.nix
@@ -3,7 +3,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
 with lib;
 
 let
-  krb5 = 
+  krb5 =
     { enable = true;
       domain_realm."nfs.test"   = "NFS.TEST";
       libdefaults.default_realm = "NFS.TEST";
@@ -31,7 +31,7 @@ in
 
 {
   name = "nfsv4-with-kerberos";
- 
+
   nodes = {
     client = { lib, ... }:
       { inherit krb5 users;
diff --git a/nixos/tests/nginx-pubhtml.nix b/nixos/tests/nginx-pubhtml.nix
index 432913cb42d..6e1e605628e 100644
--- a/nixos/tests/nginx-pubhtml.nix
+++ b/nixos/tests/nginx-pubhtml.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix {
   name = "nginx-pubhtml";
 
   machine = { pkgs, ... }: {
+    systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
     services.nginx.enable = true;
     services.nginx.virtualHosts.localhost = {
       locations."~ ^/\\~([a-z0-9_]+)(/.*)?$".alias = "/home/$1/public_html$2";
diff --git a/nixos/tests/nginx-sandbox.nix b/nixos/tests/nginx-sandbox.nix
new file mode 100644
index 00000000000..bc9d3ba8add
--- /dev/null
+++ b/nixos/tests/nginx-sandbox.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-sandbox";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ izorkin ];
+  };
+
+  # This test checks the creation and reading of a file in sandbox mode. Used simple lua script.
+
+  machine = { pkgs, ... }: {
+    nixpkgs.overlays = [
+      (self: super: {
+        nginx-lua = super.nginx.override {
+          modules = [
+            pkgs.nginxModules.lua
+          ];
+        };
+      })
+    ];
+    services.nginx.enable = true;
+    services.nginx.package = pkgs.nginx-lua;
+    services.nginx.enableSandbox = true;
+    services.nginx.virtualHosts.localhost = {
+      extraConfig = ''
+        location /test1-write {
+          content_by_lua_block {
+            local create = os.execute('${pkgs.coreutils}/bin/mkdir /tmp/test1-read')
+            local create = os.execute('${pkgs.coreutils}/bin/touch /tmp/test1-read/foo.txt')
+            local echo = os.execute('${pkgs.coreutils}/bin/echo worked > /tmp/test1-read/foo.txt')
+          }
+        }
+        location /test1-read {
+          root /tmp;
+        }
+        location /test2-write {
+          content_by_lua_block {
+            local create = os.execute('${pkgs.coreutils}/bin/mkdir /var/web/test2-read')
+            local create = os.execute('${pkgs.coreutils}/bin/touch /var/web/test2-read/bar.txt')
+            local echo = os.execute('${pkgs.coreutils}/bin/echo error-worked > /var/web/test2-read/bar.txt')
+          }
+        }
+        location /test2-read {
+          root /var/web;
+        }
+      '';
+    };
+    users.users.foo.isNormalUser = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx")
+    machine.wait_for_open_port(80)
+
+    # Checking write in temporary folder
+    machine.succeed("$(curl -vvv http://localhost/test1-write)")
+    machine.succeed('test "$(curl -fvvv http://localhost/test1-read/foo.txt)" = worked')
+
+    # Checking write in protected folder. In sandbox mode for the nginx service, the folder /var/web is mounted
+    # in read-only mode.
+    machine.succeed("mkdir -p /var/web")
+    machine.succeed("chown nginx:nginx /var/web")
+    machine.succeed("$(curl -vvv http://localhost/test2-write)")
+    assert "404 Not Found" in machine.succeed(
+        "curl -vvv -s http://localhost/test2-read/bar.txt"
+    )
+  '';
+})
diff --git a/nixos/tests/nginx-variants.nix b/nixos/tests/nginx-variants.nix
new file mode 100644
index 00000000000..ca4655391bc
--- /dev/null
+++ b/nixos/tests/nginx-variants.nix
@@ -0,0 +1,33 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+builtins.listToAttrs (
+  builtins.map
+    (nginxName:
+      {
+        name = nginxName;
+        value = makeTest {
+          name = "nginx-variant-${nginxName}";
+
+          machine = { pkgs, ... }: {
+            services.nginx = {
+              enable = true;
+              virtualHosts.localhost.locations."/".return = "200 'foo'";
+              package = pkgs."${nginxName}";
+            };
+          };
+
+          testScript = ''
+            machine.wait_for_unit("nginx")
+            machine.wait_for_open_port(80)
+            machine.succeed('test "$(curl -fvvv http://localhost/)" = foo')
+          '';
+        };
+      }
+    )
+    [ "nginxStable" "nginxUnstable" "nginxShibboleth" "openresty" "tengine" ]
+)
diff --git a/nixos/tests/pinnwand.nix b/nixos/tests/pinnwand.nix
new file mode 100644
index 00000000000..2204e74b2c2
--- /dev/null
+++ b/nixos/tests/pinnwand.nix
@@ -0,0 +1,86 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+let
+  pythonEnv = pkgs.python3.withPackages (py: with py; [ appdirs toml ]);
+
+  port = 8000;
+  baseUrl = "http://server:${toString port}";
+
+  configureSteck = pkgs.writeScript "configure.py" ''
+    #!${pythonEnv.interpreter}
+    import appdirs
+    import toml
+    import os
+
+    CONFIG = {
+      "base": "${baseUrl}/",
+      "confirm": False,
+      "magic": True,
+      "ignore": True
+    }
+
+    os.makedirs(appdirs.user_config_dir('steck'))
+    with open(os.path.join(appdirs.user_config_dir('steck'), 'steck.toml'), "w") as fd:
+        toml.dump(CONFIG, fd)
+    '';
+in
+{
+  name = "pinnwand";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers =[ hexa ];
+  };
+
+  nodes = {
+    server = { config, ... }:
+    {
+      networking.firewall.allowedTCPPorts = [
+        port
+      ];
+
+      services.pinnwand = {
+        enable = true;
+        port = port;
+      };
+    };
+
+    client = { pkgs, ... }:
+    {
+      environment.systemPackages = [ pkgs.steck ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("pinnwand.service")
+    client.wait_for_unit("network.target")
+
+    # create steck.toml config file
+    client.succeed("${configureSteck}")
+
+    # wait until the server running pinnwand is reachable
+    client.wait_until_succeeds("ping -c1 server")
+
+    # make sure pinnwand is listening
+    server.wait_until_succeeds("ss -lnp | grep ${toString port}")
+
+    # send the contents of /etc/machine-id
+    response = client.succeed("steck paste /etc/machine-id")
+
+    # parse the steck response
+    raw_url = None
+    removal_link = None
+    for line in response.split("\n"):
+        if line.startswith("View link:"):
+            raw_url = f"${baseUrl}/raw/{line.split('/')[-1]}"
+        if line.startswith("Removal link:"):
+            removal_link = line.split(":", 1)[1]
+
+    # check whether paste matches what we sent
+    client.succeed(f"curl {raw_url} > /tmp/machine-id")
+    client.succeed("diff /tmp/machine-id /etc/machine-id")
+
+    # remove paste and check that it's not available any more
+    client.succeed(f"curl {removal_link}")
+    client.fail(f"curl --fail {raw_url}")
+  '';
+})
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index 2eccfdf47f5..5a603f8cbfb 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     services.xserver.displayManager.sddm.enable = true;
     services.xserver.displayManager.defaultSession = "plasma5";
     services.xserver.desktopManager.plasma5.enable = true;
-    services.xserver.displayManager.sddm.autoLogin = {
+    services.xserver.displayManager.autoLogin = {
       enable = true;
       user = "alice";
     };
diff --git a/nixos/tests/podman.nix b/nixos/tests/podman.nix
index 283db71d9a4..cd8c2b4308c 100644
--- a/nixos/tests/podman.nix
+++ b/nixos/tests/podman.nix
@@ -12,9 +12,6 @@ import ./make-test-python.nix (
         { pkgs, ... }:
         {
           virtualisation.podman.enable = true;
-          virtualisation.containers.users = [
-            "alice"
-          ];
 
           users.users.alice = {
             isNormalUser = true;
@@ -38,23 +35,45 @@ import ./make-test-python.nix (
       start_all()
 
 
-      with subtest("Run container as root"):
+      with subtest("Run container as root with runc"):
           podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
-              "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+              "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
           podman.succeed("podman ps | grep sleeping")
           podman.succeed("podman stop sleeping")
+          podman.succeed("podman rm sleeping")
 
-      with subtest("Run container rootless"):
+      with subtest("Run container as root with crun"):
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          podman.succeed(
+              "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+          )
+          podman.succeed("podman ps | grep sleeping")
+          podman.succeed("podman stop sleeping")
+          podman.succeed("podman rm sleeping")
+
+      with subtest("Run container rootless with runc"):
+          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          podman.succeed(
+              su_cmd(
+                  "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+              )
+          )
+          podman.succeed(su_cmd("podman ps | grep sleeping"))
+          podman.succeed(su_cmd("podman stop sleeping"))
+          podman.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("Run container rootless with crun"):
           podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
           podman.succeed(
               su_cmd(
-                  "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+                  "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
               )
           )
           podman.succeed(su_cmd("podman ps | grep sleeping"))
           podman.succeed(su_cmd("podman stop sleeping"))
+          podman.succeed(su_cmd("podman rm sleeping"))
     '';
   }
 )
diff --git a/nixos/tests/postfix-raise-smtpd-tls-security-level.nix b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
new file mode 100644
index 00000000000..b3c2156122d
--- /dev/null
+++ b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
@@ -0,0 +1,44 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+in
+import ./make-test-python.nix {
+  name = "postfix";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmissions = true;
+      submissionsOptions = {
+        smtpd_tls_security_level = "none";
+      };
+    };
+
+    environment.systemPackages = let
+      checkConfig = pkgs.writeScriptBin "check-config" ''
+        #!${pkgs.python3.interpreter}
+        import sys
+
+        state = 1
+        success = False
+
+        with open("/etc/postfix/master.cf") as masterCf:
+          for line in masterCf:
+            if state == 1 and line.startswith("submissions"):
+              state = 2
+            elif state == 2 and line.startswith(" ") and "smtpd_tls_security_level=encrypt" in line:
+              success = True
+            elif state == 2 and not line.startswith(" "):
+              state == 3
+        if not success:
+          sys.exit(1)
+      '';
+
+    in [ checkConfig ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("check-config")
+  '';
+}
diff --git a/nixos/tests/postfix.nix b/nixos/tests/postfix.nix
new file mode 100644
index 00000000000..b0674ca3a0d
--- /dev/null
+++ b/nixos/tests/postfix.nix
@@ -0,0 +1,76 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+in
+import ./make-test-python.nix {
+  name = "postfix";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmission = true;
+      enableSubmissions = true;
+      sslCACert = certs.ca.cert;
+      sslCert = certs."acme.test".cert;
+      sslKey = certs."acme.test".key;
+      submissionsOptions = {
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_client_restrictions = "permit";
+          milter_macro_daemon_name = "ORIGINATING";
+      };
+    };
+
+    security.pki.certificateFiles = [
+      certs.ca.cert
+    ];
+
+    networking.extraHosts = ''
+      127.0.0.1 acme.test
+    '';
+
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+
+        with smtplib.SMTP('acme.test') as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP('acme.test') as smtp:
+          smtp.ehlo()
+          smtp.starttls(context=ctx)
+          smtp.ehlo()
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test STARTTLS\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailSmtps = pkgs.writeScriptBin "send-testmail-smtps" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP_SSL(host='acme.test', context=ctx) as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test SMTPS\n\nTest data.')
+          smtp.quit()
+      '';
+    in [ sendTestMail sendTestMailStarttls sendTestMailSmtps ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("send-testmail")
+    machine.succeed("send-testmail-starttls")
+    machine.succeed("send-testmail-smtps")
+  '';
+}
diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix
index 372dd9d8c1c..c50746aa838 100644
--- a/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixos/tests/postgresql-wal-receiver.nix
@@ -28,6 +28,10 @@ let
     meta.maintainers = with maintainers; [ pacien ];
 
     machine = { ... }: {
+      # Needed because this test uses a non-default 'services.postgresql.dataDir'.
+      systemd.tmpfiles.rules = [
+        "d /var/db/postgresql 0700 postgres postgres"
+      ];
       services.postgresql = {
         package = postgresqlPackage;
         enable = true;
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
new file mode 100644
index 00000000000..45c7cd37c24
--- /dev/null
+++ b/nixos/tests/privacyidea.nix
@@ -0,0 +1,36 @@
+# Miscellaneous small tests that don't warrant their own VM run.
+
+import ./make-test-python.nix ({ pkgs, ...} : rec {
+  name = "privacyidea";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  machine = { ... }: {
+    virtualisation.cores = 2;
+    virtualisation.memorySize = 512;
+
+    services.privacyidea = {
+      enable = true;
+      secretKey = "testing";
+      pepper = "testing";
+      adminPasswordFile = pkgs.writeText "admin-password" "testing";
+      adminEmail = "root@localhost";
+    };
+    services.nginx = {
+      enable = true;
+      virtualHosts."_".locations."/".extraConfig = ''
+        uwsgi_pass unix:/run/privacyidea/socket;
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("curl --fail http://localhost | grep privacyIDEA")
+    machine.succeed(
+        "curl --fail http://localhost/auth -F username=admin -F password=testing | grep token"
+    )
+  '';
+})
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 4fc3668cfaf..b912e3425e0 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -56,6 +56,21 @@ let
  */
 
   exporterTests = {
+     apcupsd = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.apcupsd.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("apcupsd.service")
+        wait_for_open_port(3551)
+        wait_for_unit("prometheus-apcupsd-exporter.service")
+        wait_for_open_port(9162)
+        succeed("curl -sSf http://localhost:9162/metrics | grep -q 'apcupsd_info'")
+      '';
+    };
 
     bind = {
       exporterConfig = {
@@ -202,6 +217,69 @@ let
       '';
     };
 
+    keylight = {
+      # A hardware device is required to properly test this exporter, so just
+      # perform a couple of basic sanity checks that the exporter is running
+      # and requires a target, but cannot reach a specified target.
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-keylight-exporter.service")
+        wait_for_open_port(9288)
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep -q '400'"
+        )
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep -q '500'"
+        )
+      '';
+    };
+
+    lnd = {
+      exporterConfig = {
+        enable = true;
+        lndTlsPath = "/var/lib/lnd/tls.cert";
+        lndMacaroonDir = "/var/lib/lnd";
+      };
+      metricProvider = {
+        systemd.services.prometheus-lnd-exporter.serviceConfig.DynamicUser = false;
+        services.bitcoind.enable = true;
+        services.bitcoind.extraConfig = ''
+          rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
+          bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
+          bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
+        '';
+        systemd.services.lnd = {
+          serviceConfig.ExecStart = ''
+          ${pkgs.lnd}/bin/lnd \
+            --datadir=/var/lib/lnd \
+            --tlscertpath=/var/lib/lnd/tls.cert \
+            --tlskeypath=/var/lib/lnd/tls.key \
+            --logdir=/var/log/lnd \
+            --bitcoin.active \
+            --bitcoin.mainnet \
+            --bitcoin.node=bitcoind \
+            --bitcoind.rpcuser=bitcoinrpc \
+            --bitcoind.rpcpass=hunter2 \
+            --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
+            --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
+            --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
+          '';
+          serviceConfig.StateDirectory = "lnd";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("lnd.service")
+        wait_for_open_port(10009)
+        wait_for_unit("prometheus-lnd-exporter.service")
+        wait_for_open_port(9092)
+        succeed("curl -sSf localhost:9092/metrics | grep -q '^promhttp_metric_handler'")
+      '';
+    };
+
     mail = {
       exporterConfig = {
         enable = true;
@@ -285,6 +363,31 @@ let
       '';
     };
 
+    modemmanager = {
+      exporterConfig = {
+        enable = true;
+        refreshRate = "10s";
+      };
+      metricProvider = {
+        # ModemManager is installed when NetworkManager is enabled. Ensure it is
+        # started and is wanted by NM and the exporter to start everything up
+        # in the right order.
+        networking.networkmanager.enable = true;
+        systemd.services.ModemManager = {
+          enable = true;
+          wantedBy = [ "NetworkManager.service" "prometheus-modemmanager-exporter.service" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("ModemManager.service")
+        wait_for_unit("prometheus-modemmanager-exporter.service")
+        wait_for_open_port(9539)
+        succeed(
+            "curl -sSf http://localhost:9539/metrics | grep -q 'modemmanager_info'"
+        )
+      '';
+    };
+
     nextcloud = {
       exporterConfig = {
         enable = true;
@@ -397,6 +500,20 @@ let
       '';
     };
 
+    redis = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider.services.redis.enable = true;
+      exporterTest = ''
+        wait_for_unit("redis.service")
+        wait_for_unit("prometheus-redis-exporter.service")
+        wait_for_open_port(6379)
+        wait_for_open_port(9121)
+        wait_until_succeeds("curl -sSf localhost:9121/metrics | grep -q 'redis_up 1'")
+      '';
+    };
+
     rspamd = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/pt2-clone.nix b/nixos/tests/pt2-clone.nix
new file mode 100644
index 00000000000..b502172e2ee
--- /dev/null
+++ b/nixos/tests/pt2-clone.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "pt2-clone";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.pt2-clone ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      # Add a dummy sound card, or the program won't start
+      machine.execute("modprobe snd-dummy")
+
+      machine.execute("pt2-clone &")
+
+      machine.wait_for_window(r"ProTracker")
+      machine.sleep(5)
+      # One of the few words that actually get recognized
+      if "LENGTH" not in machine.get_screen_text():
+          raise Exception("Program did not start successfully")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixos/tests/qboot.nix b/nixos/tests/qboot.nix
new file mode 100644
index 00000000000..12aef6decfa
--- /dev/null
+++ b/nixos/tests/qboot.nix
@@ -0,0 +1,13 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "qboot";
+
+  machine = { ... }: {
+    virtualisation.bios = pkgs.qboot;
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+    '';
+})
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index c81e78a8f99..1d3679c82a2 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -14,9 +14,6 @@ let
 
         [storage]
         filesystem_folder = /tmp/collections
-
-        [logging]
-        debug = True
       '';
     };
     # WARNING: DON'T DO THIS IN PRODUCTION!
@@ -49,13 +46,18 @@ in
         services.radicale.extraArgs = [
           "--export-storage" "/tmp/collections-new"
         ];
+        system.stateVersion = "17.03";
       };
       radicale2_verify = lib.recursiveUpdate radicale2 {
-        services.radicale.extraArgs = [ "--verify-storage" ];
+        services.radicale.extraArgs = [ "--debug" "--verify-storage" ];
+        system.stateVersion = "17.09";
       };
       radicale2 = lib.recursiveUpdate (common args) {
         system.stateVersion = "17.09";
       };
+      radicale3 = lib.recursiveUpdate (common args) {
+        system.stateVersion = "20.09";
+      };
     };
 
     # This tests whether the web interface is accessible to an authenticated user
@@ -117,6 +119,22 @@ in
               retcode == 0 and "VCALENDAR" in output
           ), "Could not read calendar from Radicale 2"
 
-      radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+          radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+
+      with subtest("Check Radicale 3 functionality"):
+          radicale.succeed(
+              "${switchToConfig "radicale3"} >&2"
+          )
+          radicale.wait_for_unit("radicale.service")
+          radicale.wait_for_open_port(${port})
+
+          (retcode, output) = radicale.execute(
+              "curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/"
+          )
+          assert (
+              retcode == 0 and "VCALENDAR" in output
+          ), "Could not read calendar from Radicale 3"
+
+          radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
     '';
 })
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 67bb7f1933d..dad5bdfff27 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -4,33 +4,50 @@ import ./make-test-python.nix (
     let
       password = "some_password";
       repository = "/tmp/restic-backup";
-      passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
+      rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+
+      passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
+      initialize = true;
+      paths = [ "/opt" ];
+      pruneOpts = [
+        "--keep-daily 2"
+        "--keep-weekly 1"
+        "--keep-monthly 1"
+        "--keep-yearly 99"
+      ];
     in
       {
         name = "restic";
 
         meta = with pkgs.stdenv.lib.maintainers; {
-          maintainers = [ bbigras ];
+          maintainers = [ bbigras i077 ];
         };
 
         nodes = {
           server =
-            { ... }:
+            { pkgs, ... }:
               {
                 services.restic.backups = {
                   remotebackup = {
-                    inherit repository;
-                    passwordFile = "${passwordFile}";
-                    initialize = true;
-                    paths = [ "/opt" ];
-                    pruneOpts = [
-                      "--keep-daily 2"
-                      "--keep-weekly 1"
-                      "--keep-monthly 1"
-                      "--keep-yearly 99"
-                    ];
+                    inherit repository passwordFile initialize paths pruneOpts;
+                  };
+                  rclonebackup = {
+                    repository = rcloneRepository;
+                    rcloneConfig = {
+                      type = "local";
+                      one_file_system = true;
+                    };
+
+                    # This gets overridden by rcloneConfig.type
+                    rcloneConfigFile = pkgs.writeText "rclone.conf" ''
+                      [local]
+                      type=ftp
+                    '';
+                    inherit passwordFile initialize paths pruneOpts;
                   };
                 };
+
+                environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
               };
         };
 
@@ -38,25 +55,35 @@ import ./make-test-python.nix (
           server.start()
           server.wait_for_unit("dbus.socket")
           server.fail(
-              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots"
+              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
+              "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
           )
           server.succeed(
               "mkdir -p /opt",
               "touch /opt/some_file",
+              "mkdir -p /tmp/restic-rclone-backup",
               "timedatectl set-time '2016-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
               "timedatectl set-time '2017-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-13 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-14 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-15 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               "timedatectl set-time '2018-12-16 13:45'",
               "systemctl start restic-backups-remotebackup.service",
+              "systemctl start restic-backups-rclonebackup.service",
               '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
+              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
           )
         '';
       }
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
index a145705250f..f9b961163c3 100644
--- a/nixos/tests/sddm.nix
+++ b/nixos/tests/sddm.nix
@@ -44,8 +44,8 @@ let
       machine = { ... }: {
         imports = [ ./common/user-account.nix ];
         services.xserver.enable = true;
-        services.xserver.displayManager.sddm = {
-          enable = true;
+        services.xserver.displayManager = {
+          sddm.enable = true;
           autoLogin = {
             enable = true;
             user = "alice";
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
new file mode 100644
index 00000000000..cf6ee8db80b
--- /dev/null
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "shattered-pixel-dungeon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.shattered-pixel-dungeon ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("shattered-pixel-dungeon &")
+      machine.wait_for_window(r"Shattered Pixel Dungeon")
+      machine.sleep(5)
+      if "Enter" not in machine.get_screen_text():
+          raise Exception("Program did not start successfully")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix
index d0e62d15437..a54c5d9db48 100644
--- a/nixos/tests/slurm.nix
+++ b/nixos/tests/slurm.nix
@@ -1,16 +1,52 @@
-import ./make-test-python.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, pkgs, ... }:
 let
-    mungekey = "mungeverryweakkeybuteasytointegratoinatest";
-
     slurmconfig = {
-      controlMachine = "control";
-      nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
-      partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
-      extraConfig = ''
-        AccountingStorageHost=dbd
-        AccountingStorageType=accounting_storage/slurmdbd
-      '';
+      services.slurm = {
+        controlMachine = "control";
+        nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
+        partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
+        extraConfig = ''
+          AccountingStorageHost=dbd
+          AccountingStorageType=accounting_storage/slurmdbd
+        '';
+      };
+      environment.systemPackages = [ mpitest ];
+      networking.firewall.enable = false;
+      systemd.tmpfiles.rules = [
+        "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+      ];
     };
+
+    mpitest = let
+      mpitestC = pkgs.writeText "mpitest.c" ''
+        #include <stdio.h>
+        #include <stdlib.h>
+        #include <mpi.h>
+
+        int
+        main (int argc, char *argv[])
+        {
+          int rank, size, length;
+          char name[512];
+
+          MPI_Init (&argc, &argv);
+          MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+          MPI_Comm_size (MPI_COMM_WORLD, &size);
+          MPI_Get_processor_name (name, &length);
+
+          if ( rank == 0 ) printf("size=%d\n", size);
+
+          printf ("%s: hello world from process %d of %d\n", name, rank, size);
+
+          MPI_Finalize ();
+
+          return EXIT_SUCCESS;
+        }
+      '';
+    in pkgs.runCommandNoCC "mpitest" {} ''
+      mkdir -p $out/bin
+      ${pkgs.openmpi}/bin/mpicc ${mpitestC} -o $out/bin/mpitest
+    '';
 in {
   name = "slurm";
 
@@ -21,37 +57,40 @@ in {
     computeNode =
       { ...}:
       {
+        imports = [ slurmconfig ];
         # TODO slurmd port and slurmctld port should be configurations and
         # automatically allowed by the  firewall.
-        networking.firewall.enable = false;
         services.slurm = {
           client.enable = true;
-        } // slurmconfig;
+        };
       };
     in {
 
     control =
       { ...}:
       {
-        networking.firewall.enable = false;
+        imports = [ slurmconfig ];
         services.slurm = {
           server.enable = true;
-        } // slurmconfig;
+        };
       };
 
     submit =
       { ...}:
       {
-        networking.firewall.enable = false;
+        imports = [ slurmconfig ];
         services.slurm = {
           enableStools = true;
-        } // slurmconfig;
+        };
       };
 
     dbd =
       { pkgs, ... } :
       {
         networking.firewall.enable = false;
+        systemd.tmpfiles.rules = [
+          "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+        ];
         services.slurm.dbdserver = {
           enable = true;
           storagePass = "password123";
@@ -87,24 +126,7 @@ in {
   ''
   start_all()
 
-  # Set up authentification across the cluster
-  for node in [submit, control, dbd, node1, node2, node3]:
-
-      node.wait_for_unit("default.target")
-
-      node.succeed("mkdir /etc/munge")
-      node.succeed(
-          "echo '${mungekey}' > /etc/munge/munge.key"
-      )
-      node.succeed("chmod 0400 /etc/munge/munge.key")
-      node.succeed("chown munge:munge /etc/munge/munge.key")
-      node.succeed("systemctl restart munged")
-
-      node.wait_for_unit("munged")
-
-
-  # Restart the services since they have probably failed due to the munge init
-  # failure
+  # Make sure DBD is up after DB initialzation
   with subtest("can_start_slurmdbd"):
       dbd.succeed("systemctl restart slurmdbd")
       dbd.wait_for_unit("slurmdbd.service")
@@ -137,5 +159,8 @@ in {
           # find the srun job from above in the database
           control.succeed("sleep 5")
           control.succeed("sacct | grep hostname")
+
+  with subtest("run_PMIx_mpitest"):
+      submit.succeed("srun -N 3 --mpi=pmix mpitest | grep size=3")
   '';
 })
diff --git a/nixos/tests/snapcast.nix b/nixos/tests/snapcast.nix
new file mode 100644
index 00000000000..92534f10281
--- /dev/null
+++ b/nixos/tests/snapcast.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  port = 10004;
+  tcpPort = 10005;
+  httpPort = 10080;
+in {
+  name = "snapcast";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hexa ];
+  };
+
+  nodes = {
+    server = {
+      services.snapserver = {
+        enable = true;
+        port = port;
+        tcp.port = tcpPort;
+        http.port = httpPort;
+        streams = {
+          mpd = {
+            type = "pipe";
+            location = "/run/snapserver/mpd";
+          };
+          bluetooth = {
+            type = "pipe";
+            location = "/run/snapserver/bluetooth";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    get_rpc_version = {"id": "1", "jsonrpc": "2.0", "method": "Server.GetRPCVersion"}
+
+    start_all()
+
+    server.wait_for_unit("snapserver.service")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString port}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString tcpPort}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString httpPort}")
+
+    with subtest("check that pipes are created"):
+        server.succeed("test -p /run/snapserver/mpd")
+        server.succeed("test -p /run/snapserver/bluetooth")
+
+    with subtest("test tcp json-rpc"):
+        server.succeed(f"echo '{json.dumps(get_rpc_version)}' | nc -w 1 localhost ${toString tcpPort}")
+
+    with subtest("test http json-rpc"):
+        server.succeed(
+            "curl --fail http://localhost:${toString httpPort}/jsonrpc -d '{json.dumps(get_rpc_version)}'"
+        )
+  '';
+})
diff --git a/nixos/tests/sogo.nix b/nixos/tests/sogo.nix
new file mode 100644
index 00000000000..016331a9eed
--- /dev/null
+++ b/nixos/tests/sogo.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "sogo";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ajs124 das_j ];
+  };
+
+  nodes = {
+    sogo = { config, pkgs, ... }: {
+      services.nginx.enable = true;
+
+      services.mysql = {
+        enable = true;
+        package = pkgs.mysql;
+        ensureDatabases = [ "sogo" ];
+        ensureUsers = [{
+          name = "sogo";
+          ensurePermissions = {
+            "sogo.*" = "ALL PRIVILEGES";
+          };
+        }];
+      };
+
+      services.sogo = {
+        enable = true;
+        timezone = "Europe/Berlin";
+        extraConfig = ''
+          WOWorkersCount = 1;
+
+          SOGoUserSources = (
+            {
+              type = sql;
+              userPasswordAlgorithm = md5;
+              viewURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_users";
+              canAuthenticate = YES;
+              id = users;
+            }
+          );
+
+          SOGoProfileURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_user_profile";
+          OCSFolderInfoURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_folder_info";
+          OCSSessionsFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_sessions_folder";
+          OCSEMailAlarmsFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_alarms_folder";
+          OCSStoreURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_store";
+          OCSAclURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_acl";
+          OCSCacheFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_cache_folder";
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    sogo.wait_for_unit("multi-user.target")
+    sogo.wait_for_open_port(20000)
+    sogo.wait_for_open_port(80)
+    sogo.succeed("curl -sSfL http://sogo/SOGo")
+  '';
+})
diff --git a/nixos/tests/sslh.nix b/nixos/tests/sslh.nix
new file mode 100644
index 00000000000..2a800aa52d0
--- /dev/null
+++ b/nixos/tests/sslh.nix
@@ -0,0 +1,83 @@
+import ./make-test-python.nix {
+  name = "sslh";
+
+  nodes = {
+    server = { pkgs, lib, ... }: {
+      networking.firewall.allowedTCPPorts = [ 443 ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::2";
+          prefixLength = 64;
+        }
+      ];
+      # sslh is really slow when reverse dns does not work
+      networking.hosts = {
+        "fe00:aa:bb:cc::2" = [ "server" ];
+        "fe00:aa:bb:cc::1" = [ "client" ];
+      };
+      services.sslh = {
+        enable = true;
+        transparent = true;
+        appendConfig = ''
+          protocols:
+          (
+            { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
+            { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
+          );
+        '';
+      };
+      services.openssh.enable = true;
+      users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."localhost" = {
+          addSSL = false;
+          default = true;
+          root = pkgs.runCommand "testdir" {} ''
+            mkdir "$out"
+            echo hello world > "$out/index.html"
+          '';
+        };
+      };
+    };
+    client = { ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::1";
+          prefixLength = 64;
+        }
+      ];
+      networking.hosts."fe00:aa:bb:cc::2" = [ "server" ];
+      environment.etc.sshKey = {
+        source = ./initrd-network-ssh/id_ed25519; # dont use this anywhere else
+        mode = "0600";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("sslh.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("sshd.service")
+    server.wait_for_open_port(80)
+    server.wait_for_open_port(443)
+    server.wait_for_open_port(22)
+
+    for arg in ["-6", "-4"]:
+        client.wait_until_succeeds(f"ping {arg} -c1 server")
+
+        # check that ssh through sslh works
+        client.succeed(
+            f"ssh {arg} -p 443 -i /etc/sshKey -o StrictHostKeyChecking=accept-new server 'echo $SSH_CONNECTION > /tmp/foo{arg}'"
+        )
+
+        # check that 1/ the above ssh command had an effect 2/ transparent proxying really works
+        ip = "fe00:aa:bb:cc::1" if arg == "-6" else "192.168.1."
+        server.succeed(f"grep '{ip}' /tmp/foo{arg}")
+
+        # check that http through sslh works
+        assert client.succeed(f"curl {arg} http://server:443").strip() == "hello world"
+  '';
+}
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
index 5bbec3d5726..8c38f1b47ef 100644
--- a/nixos/tests/sudo.nix
+++ b/nixos/tests/sudo.nix
@@ -74,7 +74,7 @@ in
         with subtest("test5 user should not be able to run commands under root"):
             machine.fail("sudo -u test5 sudo -n -u root true")
 
-        with subtest("test5 user should be able to keep his environment"):
+        with subtest("test5 user should be able to keep their environment"):
             machine.succeed("sudo -u test5 sudo -n -E -u test1 true")
 
         with subtest("users in group 'barfoo' should not be able to keep their environment"):
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
index 9c8e0a3d087..0a01da52b68 100644
--- a/nixos/tests/syncthing-init.nix
+++ b/nixos/tests/syncthing-init.nix
@@ -24,9 +24,8 @@ in {
   testScript = ''
     machine.wait_for_unit("syncthing-init.service")
     config = machine.succeed("cat /var/lib/syncthing/.config/syncthing/config.xml")
-   
+
     assert "testFolder" in config
     assert "${testId}" in config
   '';
 })
-
diff --git a/nixos/tests/syncthing.nix b/nixos/tests/syncthing.nix
new file mode 100644
index 00000000000..9e2a8e01e3f
--- /dev/null
+++ b/nixos/tests/syncthing.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "syncthing";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ chkno ];
+
+  nodes = rec {
+    a = {
+      environment.systemPackages = with pkgs; [ curl libxml2 syncthing ];
+      services.syncthing = {
+        enable = true;
+        openDefaultPorts = true;
+      };
+    };
+    b = a;
+  };
+
+  testScript = ''
+    import json
+    import shlex
+
+    confdir = "/var/lib/syncthing/.config/syncthing"
+
+
+    def addPeer(host, name, deviceID):
+        APIKey = host.succeed(
+            "xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir
+        ).strip()
+        oldConf = host.succeed(
+            "curl -Ss -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config" % APIKey
+        )
+        conf = json.loads(oldConf)
+        conf["devices"].append({"deviceID": deviceID, "id": name})
+        conf["folders"].append(
+            {
+                "devices": [{"deviceID": deviceID}],
+                "id": "foo",
+                "path": "/var/lib/syncthing/foo",
+                "rescanIntervalS": 1,
+            }
+        )
+        newConf = json.dumps(conf)
+        host.succeed(
+            "curl -Ss -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config -d %s"
+            % (APIKey, shlex.quote(newConf))
+        )
+
+
+    start_all()
+    a.wait_for_unit("syncthing.service")
+    b.wait_for_unit("syncthing.service")
+    a.wait_for_open_port(22000)
+    b.wait_for_open_port(22000)
+
+    aDeviceID = a.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    bDeviceID = b.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    addPeer(a, "b", bDeviceID)
+    addPeer(b, "a", aDeviceID)
+
+    a.wait_for_file("/var/lib/syncthing/foo")
+    b.wait_for_file("/var/lib/syncthing/foo")
+    a.succeed("echo a2b > /var/lib/syncthing/foo/a2b")
+    b.succeed("echo b2a > /var/lib/syncthing/foo/b2a")
+    a.wait_for_file("/var/lib/syncthing/foo/b2a")
+    b.wait_for_file("/var/lib/syncthing/foo/a2b")
+  '';
+})
diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix
new file mode 100644
index 00000000000..2a676f3da98
--- /dev/null
+++ b/nixos/tests/systemd-binfmt.nix
@@ -0,0 +1,24 @@
+# Teach the kernel how to run armv7l and aarch64-linux binaries,
+# and run GNU Hello for these architectures.
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd-binfmt";
+  machine = {
+    boot.binfmt.emulatedSystems = [
+      "armv7l-linux"
+      "aarch64-linux"
+    ];
+  };
+
+  testScript = let
+    helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
+    helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
+  in ''
+    machine.start()
+    assert "world" in machine.succeed(
+        "${helloArmv7l}/bin/hello"
+    )
+    assert "world" in machine.succeed(
+        "${helloAarch64}/bin/hello"
+    )
+  '';
+})
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index e911c393361..7a663dd9b42 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -6,26 +6,85 @@
 with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
-makeTest {
-  name = "systemd-boot";
-  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
-
-  machine = { pkgs, lib, ... }: {
+let
+  common = {
     virtualisation.useBootLoader = true;
     virtualisation.useEFIBoot = true;
     boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+    environment.systemPackages = [ pkgs.efibootmgr ];
   };
+in
+{
+  basic = makeTest {
+    name = "systemd-boot";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
+
+    machine = common;
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should have created an EFI entry
+      machine.succeed('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
+  fallback = makeTest {
+    name = "systemd-boot-fallback";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
+
+    machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.efi.canTouchEfiVariables = mkForce false;
+    };
 
-  testScript = ''
-    machine.start()
-    machine.wait_for_unit("multi-user.target")
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
 
-    machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
 
-    # Ensure we actually booted using systemd-boot.
-    # Magic number is the vendor UUID used by systemd-boot.
-    machine.succeed(
-        "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
-    )
-  '';
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should _not_ have created an EFI entry
+      machine.fail('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  update = makeTest {
+    name = "systemd-boot-update";
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ danielfullmer ];
+
+    machine = common;
+
+    testScript = ''
+      machine.succeed("mount -o remount,rw /boot")
+
+      # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
+      machine.succeed(
+          """
+        find /boot -iname '*.efi' -print0 | \
+        xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 001 ####/' '{}'
+      """
+      )
+
+      output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
+      assert "updating systemd-boot from 001 to " in output
+    '';
+  };
 }
diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix
index af7813a2e60..bd4751f8e43 100644
--- a/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixos/tests/systemd-networkd-vrf.nix
@@ -159,6 +159,8 @@ in {
     node2.wait_for_unit("network.target")
     node3.wait_for_unit("network.target")
 
+    # NOTE: please keep in mind that the trailing whitespaces in the following strings
+    # are intentional as the output is compared against the raw `iproute2`-output.
     client_ipv4_table = """
     192.168.1.2 dev vrf1 proto static metric 100 
     192.168.2.3 dev vrf2 proto static metric 100
@@ -194,18 +196,16 @@ in {
         client.succeed("ping -c5 192.168.1.2")
         client.succeed("ping -c5 192.168.2.3")
 
-    # Test whether SSH through a VRF IP is possible.
-    # (Note: this seems to be an issue on Linux 5.x, so I decided to add this to
-    # ensure that we catch this when updating the default kernel).
-    # with subtest("tcp traffic through vrf works"):
-    #     node1.wait_for_open_port(22)
-    #     client.succeed(
-    #         "cat ${snakeOilPrivateKey} > privkey.snakeoil"
-    #     )
-    #     client.succeed("chmod 600 privkey.snakeoil")
-    #     client.succeed(
-    #         "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
-    #     )
+    # Test whether TCP through a VRF IP is possible.
+    with subtest("tcp traffic through vrf works"):
+        node1.wait_for_open_port(22)
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
+        )
 
     # Only configured routes through the VRF from the main routing table should
     # work. Additional IPs are only reachable when binding to the vrf interface.
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index ca2e36a443e..a653932fb37 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -50,6 +50,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         fi
       '';
     };
+
+    systemd.watchdog = {
+      device = "/dev/watchdog";
+      runtimeTime = "30s";
+      rebootTime = "10min";
+      kexecTime = "5min";
+    };
   };
 
   testScript = ''
@@ -97,6 +104,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             re.search(r"^Filesystem state: *clean$", extinfo, re.MULTILINE) is not None
         ), ("File system was not cleanly unmounted: " + extinfo)
 
+    # Regression test for https://github.com/NixOS/nixpkgs/pull/91232
+    with subtest("setting transient hostnames works"):
+        machine.succeed("hostnamectl set-hostname --transient machine-transient")
+        machine.fail("hostnamectl set-hostname machine-all")
+
     with subtest("systemd-shutdown works"):
         machine.shutdown()
         machine.wait_for_unit("multi-user.target")
@@ -117,5 +129,20 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         retcode, output = machine.execute("systemctl status testservice1.service")
         assert retcode in [0, 3]  # https://bugs.freedesktop.org/show_bug.cgi?id=77507
         assert "CPU:" in output
+
+    # Test systemd is configured to manage a watchdog
+    with subtest("systemd manages hardware watchdog"):
+        machine.wait_for_unit("multi-user.target")
+
+        # It seems that the device's path doesn't appear in 'systemctl show' so
+        # check it separately.
+        assert "WatchdogDevice=/dev/watchdog" in machine.succeed(
+            "cat /etc/systemd/system.conf"
+        )
+
+        output = machine.succeed("systemctl show | grep Watchdog")
+        assert "RuntimeWatchdogUSec=30s" in output
+        assert "RebootWatchdogUSec=10m" in output
+        assert "KExecWatchdogUSec=5m" in output
   '';
 })
diff --git a/nixos/tests/taskserver.nix b/nixos/tests/taskserver.nix
index ab9b589f859..f34782c7059 100644
--- a/nixos/tests/taskserver.nix
+++ b/nixos/tests/taskserver.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }: let
+import ./make-test-python.nix ({ pkgs, ... }: let
   snakeOil = pkgs.runCommand "snakeoil-certs" {
     outputs = [ "out" "cacert" "cert" "key" "crl" ];
     buildInputs = [ pkgs.gnutls.bin ];
@@ -105,186 +105,178 @@ in {
     newServerSystem = nodes.newServer.config.system.build.toplevel;
     switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
   in ''
-    sub su ($$) {
-      my ($user, $cmd) = @_;
-      my $esc = $cmd =~ s/'/'\\${"'"}'/gr;
-      return "su - $user -c '$esc'";
-    }
-
-    sub setupClientsFor ($$;$) {
-      my ($org, $user, $extraInit) = @_;
-
-      for my $client ($client1, $client2) {
-        $client->nest("initialize client for user $user", sub {
-          $client->succeed(
-            (su $user, "rm -rf /home/$user/.task"),
-            (su $user, "task rc.confirmation=no config confirmation no")
-          );
-
-          my $exportinfo = $server->succeed(
-            "nixos-taskserver user export $org $user"
-          );
-
-          $exportinfo =~ s/'/'\\'''/g;
-
-          $client->nest("importing taskwarrior configuration", sub {
-            my $cmd = su $user, "eval '$exportinfo' >&2";
-            my ($status, $out) = $client->execute_($cmd);
-            if ($status != 0) {
-              $client->log("output: $out");
-              die "command `$cmd' did not succeed (exit code $status)\n";
-            }
-          });
-
-          eval { &$extraInit($client, $org, $user) };
-
-          $client->succeed(su $user,
-            "task config taskd.server server:${portStr} >&2"
-          );
-
-          $client->succeed(su $user, "task sync init >&2");
-        });
-      }
-    }
-
-    sub restartServer {
-      $server->succeed("systemctl restart taskserver.service");
-      $server->waitForOpenPort(${portStr});
-    }
-
-    sub readdImperativeUser {
-      $server->nest("(re-)add imperative user bar", sub {
-        $server->execute("nixos-taskserver org remove imperativeOrg");
-        $server->succeed(
-          "nixos-taskserver org add imperativeOrg",
-          "nixos-taskserver user add imperativeOrg bar"
-        );
-        setupClientsFor "imperativeOrg", "bar";
-      });
-    }
-
-    sub testSync ($) {
-      my $user = $_[0];
-      subtest "sync for user $user", sub {
-        $client1->succeed(su $user, "task add foo >&2");
-        $client1->succeed(su $user, "task sync >&2");
-        $client2->fail(su $user, "task list >&2");
-        $client2->succeed(su $user, "task sync >&2");
-        $client2->succeed(su $user, "task list >&2");
-      };
-    }
-
-    sub checkClientCert ($) {
-      my $user = $_[0];
-      my $cmd = "gnutls-cli".
-        " --x509cafile=/home/$user/.task/keys/ca.cert".
-        " --x509keyfile=/home/$user/.task/keys/private.key".
-        " --x509certfile=/home/$user/.task/keys/public.cert".
-        " --port=${portStr} server < /dev/null";
-      return su $user, $cmd;
-    }
+    from shlex import quote
+
+
+    def su(user, cmd):
+        return f"su - {user} -c {quote(cmd)}"
+
+
+    def no_extra_init(client, org, user):
+        pass
+
+
+    def setup_clients_for(org, user, extra_init=no_extra_init):
+        for client in [client1, client2]:
+            with client.nested(f"initialize client for user {user}"):
+                client.succeed(
+                    su(user, f"rm -rf /home/{user}/.task"),
+                    su(user, "task rc.confirmation=no config confirmation no"),
+                )
+
+                exportinfo = server.succeed(f"nixos-taskserver user export {org} {user}")
+
+                with client.nested("importing taskwarrior configuration"):
+                    client.succeed(su(user, f"eval {quote(exportinfo)} >&2"))
+
+                extra_init(client, org, user)
+
+                client.succeed(su(user, "task config taskd.server server:${portStr} >&2"))
+
+                client.succeed(su(user, "task sync init >&2"))
+
+
+    def restart_server():
+        server.systemctl("restart taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+
+    def re_add_imperative_user():
+        with server.nested("(re-)add imperative user bar"):
+            server.execute("nixos-taskserver org remove imperativeOrg")
+            server.succeed(
+                "nixos-taskserver org add imperativeOrg",
+                "nixos-taskserver user add imperativeOrg bar",
+            )
+            setup_clients_for("imperativeOrg", "bar")
+
+
+    def test_sync(user):
+        with subtest(f"sync for user {user}"):
+            client1.succeed(su(user, "task add foo >&2"))
+            client1.succeed(su(user, "task sync >&2"))
+            client2.fail(su(user, "task list >&2"))
+            client2.succeed(su(user, "task sync >&2"))
+            client2.succeed(su(user, "task list >&2"))
+
+
+    def check_client_cert(user):
+        # debug level 3 is a workaround for gnutls issue https://gitlab.com/gnutls/gnutls/-/issues/1040
+        cmd = (
+            f"gnutls-cli -d 3"
+            f" --x509cafile=/home/{user}/.task/keys/ca.cert"
+            f" --x509keyfile=/home/{user}/.task/keys/private.key"
+            f" --x509certfile=/home/{user}/.task/keys/public.cert"
+            f" --port=${portStr} server < /dev/null"
+        )
+        return su(user, cmd)
+
 
     # Explicitly start the VMs so that we don't accidentally start newServer
-    $server->start;
-    $client1->start;
-    $client2->start;
+    server.start()
+    client1.start()
+    client2.start()
 
-    $server->waitForUnit("taskserver.service");
+    server.wait_for_unit("taskserver.service")
 
-    $server->succeed(
-      "nixos-taskserver user list testOrganisation | grep -qxF alice",
-      "nixos-taskserver user list testOrganisation | grep -qxF foo",
-      "nixos-taskserver user list anotherOrganisation | grep -qxF bob"
-    );
+    server.succeed(
+        "nixos-taskserver user list testOrganisation | grep -qxF alice",
+        "nixos-taskserver user list testOrganisation | grep -qxF foo",
+        "nixos-taskserver user list anotherOrganisation | grep -qxF bob",
+    )
 
-    $server->waitForOpenPort(${portStr});
+    server.wait_for_open_port(${portStr})
 
-    $client1->waitForUnit("multi-user.target");
-    $client2->waitForUnit("multi-user.target");
+    client1.wait_for_unit("multi-user.target")
+    client2.wait_for_unit("multi-user.target")
 
-    setupClientsFor "testOrganisation", "alice";
-    setupClientsFor "testOrganisation", "foo";
-    setupClientsFor "anotherOrganisation", "bob";
+    setup_clients_for("testOrganisation", "alice")
+    setup_clients_for("testOrganisation", "foo")
+    setup_clients_for("anotherOrganisation", "bob")
 
-    testSync $_ for ("alice", "bob", "foo");
+    for user in ["alice", "bob", "foo"]:
+        test_sync(user)
 
-    $server->fail("nixos-taskserver user add imperativeOrg bar");
-    readdImperativeUser;
+    server.fail("nixos-taskserver user add imperativeOrg bar")
+    re_add_imperative_user()
 
-    testSync "bar";
+    test_sync("bar")
 
-    subtest "checking certificate revocation of user bar", sub {
-      $client1->succeed(checkClientCert "bar");
+    with subtest("checking certificate revocation of user bar"):
+        client1.succeed(check_client_cert("bar"))
 
-      $server->succeed("nixos-taskserver user remove imperativeOrg bar");
-      restartServer;
+        server.succeed("nixos-taskserver user remove imperativeOrg bar")
+        restart_server()
 
-      $client1->fail(checkClientCert "bar");
+        client1.fail(check_client_cert("bar"))
 
-      $client1->succeed(su "bar", "task add destroy everything >&2");
-      $client1->fail(su "bar", "task sync >&2");
-    };
+        client1.succeed(su("bar", "task add destroy everything >&2"))
+        client1.fail(su("bar", "task sync >&2"))
 
-    readdImperativeUser;
+    re_add_imperative_user()
 
-    subtest "checking certificate revocation of org imperativeOrg", sub {
-      $client1->succeed(checkClientCert "bar");
+    with subtest("checking certificate revocation of org imperativeOrg"):
+        client1.succeed(check_client_cert("bar"))
 
-      $server->succeed("nixos-taskserver org remove imperativeOrg");
-      restartServer;
+        server.succeed("nixos-taskserver org remove imperativeOrg")
+        restart_server()
 
-      $client1->fail(checkClientCert "bar");
+        client1.fail(check_client_cert("bar"))
 
-      $client1->succeed(su "bar", "task add destroy even more >&2");
-      $client1->fail(su "bar", "task sync >&2");
-    };
+        client1.succeed(su("bar", "task add destroy even more >&2"))
+        client1.fail(su("bar", "task sync >&2"))
 
-    readdImperativeUser;
+    re_add_imperative_user()
 
-    subtest "check whether declarative config overrides user bar", sub {
-      restartServer;
-      testSync "bar";
-    };
+    with subtest("check whether declarative config overrides user bar"):
+        restart_server()
+        test_sync("bar")
 
-    subtest "check manual configuration", sub {
-      # Remove the keys from automatic CA creation, to make sure the new
-      # generation doesn't use keys from before.
-      $server->succeed('rm -rf ${cfg.dataDir}/keys/* >&2');
-
-      $server->succeed('${switchToNewServer} >&2');
-      $server->waitForUnit("taskserver.service");
-      $server->waitForOpenPort(${portStr});
-
-      $server->succeed(
-        "nixos-taskserver org add manualOrg",
-        "nixos-taskserver user add manualOrg alice"
-      );
-
-      setupClientsFor "manualOrg", "alice", sub {
-        my ($client, $org, $user) = @_;
-        my $cfgpath = "/home/$user/.task";
-
-        $client->copyFileFromHost("${snakeOil.cacert}", "$cfgpath/ca.cert");
-        for my $file ('alice.key', 'alice.cert') {
-          $client->copyFileFromHost("${snakeOil}/$file", "$cfgpath/$file");
-        }
-
-        for my $file ("$user.key", "$user.cert") {
-          $client->copyFileFromHost(
-            "${snakeOil}/$file", "$cfgpath/$file"
-          );
-        }
-        $client->copyFileFromHost(
-          "${snakeOil.cacert}", "$cfgpath/ca.cert"
-        );
-        $client->succeed(
-          (su "alice", "task config taskd.ca $cfgpath/ca.cert"),
-          (su "alice", "task config taskd.key $cfgpath/$user.key"),
-          (su $user, "task config taskd.certificate $cfgpath/$user.cert")
-        );
-      };
 
-      testSync "alice";
-    };
+    def init_manual_config(client, org, user):
+        cfgpath = f"/home/{user}/.task"
+
+        client.copy_from_host(
+            "${snakeOil.cacert}",
+            f"{cfgpath}/ca.cert",
+        )
+        for file in ["alice.key", "alice.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        for file in [f"{user}.key", f"{user}.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        client.succeed(
+            su("alice", f"task config taskd.ca {cfgpath}/ca.cert"),
+            su("alice", f"task config taskd.key {cfgpath}/{user}.key"),
+            su(user, f"task config taskd.certificate {cfgpath}/{user}.cert"),
+        )
+
+
+    with subtest("check manual configuration"):
+        # Remove the keys from automatic CA creation, to make sure the new
+        # generation doesn't use keys from before.
+        server.succeed("rm -rf ${cfg.dataDir}/keys/* >&2")
+
+        server.succeed(
+            "${switchToNewServer} >&2"
+        )
+        server.wait_for_unit("taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+        server.succeed(
+            "nixos-taskserver org add manualOrg",
+            "nixos-taskserver user add manualOrg alice",
+        )
+
+        setup_clients_for("manualOrg", "alice", init_manual_config)
+
+        test_sync("alice")
   '';
 })
diff --git a/nixos/tests/teeworlds.nix b/nixos/tests/teeworlds.nix
new file mode 100644
index 00000000000..edf58896878
--- /dev/null
+++ b/nixos/tests/teeworlds.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.teeworlds ];
+    };
+
+in {
+  name = "teeworlds";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes =
+    { server =
+      { services.teeworlds = {
+          enable = true;
+          openPorts = true;
+        };
+      };
+
+      client1 = client;
+      client2 = client;
+    };
+
+    testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("teeworlds.service")
+      server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 8303")
+
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("teeworlds 'player_name Alice;connect server'&")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Alice"'
+      )
+
+      client2.execute("teeworlds 'player_name Bob;connect server'&")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Bob"'
+      )
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.screenshot("screen_client1")
+      client2.screenshot("screen_client2")
+    '';
+
+})
diff --git a/nixos/tests/tiddlywiki.nix b/nixos/tests/tiddlywiki.nix
index cf45578b0f9..822711b8939 100644
--- a/nixos/tests/tiddlywiki.nix
+++ b/nixos/tests/tiddlywiki.nix
@@ -44,7 +44,7 @@ import ./make-test-python.nix ({ ... }: {
           configured.succeed(
               "curl --fail -o /dev/null 127.0.0.1:3000 --user somelogin:somesecret"
           )
-      
+
       with subtest("restart preserves changes"):
           # given running wiki
           default.wait_for_unit("tiddlywiki.service")
diff --git a/nixos/tests/transmission.nix b/nixos/tests/transmission.nix
index f4f2186be1f..37c0352dcfb 100644
--- a/nixos/tests/transmission.nix
+++ b/nixos/tests/transmission.nix
@@ -9,6 +9,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     networking.firewall.allowedTCPPorts = [ 9091 ];
 
+    security.apparmor.enable = true;
+
     services.transmission.enable = true;
   };
 
diff --git a/nixos/tests/trezord.nix b/nixos/tests/trezord.nix
index 8d908a52249..b7b3dd31942 100644
--- a/nixos/tests/trezord.nix
+++ b/nixos/tests/trezord.nix
@@ -1,9 +1,8 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "trezord";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ mmahut "1000101" ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ mmahut _1000101 ];
   };
-
   nodes = {
     machine = { ... }: {
       services.trezord.enable = true;
diff --git a/nixos/tests/trickster.nix b/nixos/tests/trickster.nix
index e2ca00980d5..713ac8f0b2f 100644
--- a/nixos/tests/trickster.nix
+++ b/nixos/tests/trickster.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "trickster";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ "1000101" ];
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ _1000101 ];
   };
 
   nodes = {
@@ -34,4 +34,4 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "curl -L http://localhost:9090/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
     )
   '';
-})
\ No newline at end of file
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index aec8da6a2af..af76e6f9844 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -198,7 +198,6 @@ let
       systemd.services."vboxtestlog-${name}@" = {
         description = "VirtualBox Test Machine Log For ${name}";
         serviceConfig.StandardInput = "socket";
-        serviceConfig.StandardOutput = "syslog";
         serviceConfig.SyslogIdentifier = "GUEST-${name}";
         serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat";
       };
diff --git a/nixos/tests/wasabibackend.nix b/nixos/tests/wasabibackend.nix
new file mode 100644
index 00000000000..d169ad15272
--- /dev/null
+++ b/nixos/tests/wasabibackend.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "wasabibackend";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.wasabibackend = {
+        enable = true;
+        network = "testnet";
+        rpc = {
+          user = "alice";
+          port = 18332;
+        };
+      };
+      services.bitcoind = {
+        enable = true;
+        testnet = true;
+        rpc.users = {
+          alice.passwordHMAC = "e7096bc21da60b29ecdbfcdb2c3acc62$f948e61cb587c399358ed99c6ed245a41460b4bf75125d8330c9f6fcc13d7ae7";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("wasabibackend.service")
+    machine.wait_until_succeeds(
+        "grep 'Wasabi Backend started' /var/lib/wasabibackend/.walletwasabi/backend/Logs.txt"
+    )
+    machine.sleep(5)
+    machine.succeed(
+        "grep 'Config is successfully initialized' /var/lib/wasabibackend/.walletwasabi/backend/Logs.txt"
+    )
+  '';
+})
diff --git a/nixos/tests/web-servers/unit-php.nix b/nixos/tests/web-servers/unit-php.nix
index c6327a1f825..2a0a5bdaa5d 100644
--- a/nixos/tests/web-servers/unit-php.nix
+++ b/nixos/tests/web-servers/unit-php.nix
@@ -23,7 +23,10 @@ in {
               "user": "testuser",
               "group": "testgroup",
               "root": "${testdir}/www",
-              "index": "info.php"
+              "index": "info.php",
+              "options": {
+                "file": "${pkgs.unit.usedPhp74}/lib/php.ini"
+              }
             }
           }
         }
@@ -42,6 +45,13 @@ in {
   };
   testScript = ''
     machine.wait_for_unit("unit.service")
-    assert "PHP Version ${pkgs.php74.version}" in machine.succeed("curl -vvv -s http://127.0.0.1:9074/")
+
+    # Check so we get an evaluated PHP back
+    response = machine.succeed("curl -vvv -s http://127.0.0.1:9074/")
+    assert "PHP Version ${pkgs.unit.usedPhp74.version}" in response, "PHP version not detected"
+
+    # Check so we have database and some other extensions loaded
+    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
+        assert ext in response, f"Missing {ext} extension"
   '';
 })
diff --git a/nixos/tests/wireguard/basic.nix b/nixos/tests/wireguard/basic.nix
new file mode 100644
index 00000000000..25d706ae2e5
--- /dev/null
+++ b/nixos/tests/wireguard/basic.nix
@@ -0,0 +1,74 @@
+{ kernelPackages ? null }:
+import ../make-test-python.nix ({ pkgs, lib, ...} :
+  let
+    wg-snakeoil-keys = import ./snakeoil-keys.nix;
+    peer = (import ./make-peer.nix) { inherit lib; };
+  in
+  {
+    name = "wireguard";
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ ma27 ];
+    };
+
+    nodes = {
+      peer0 = peer {
+        ip4 = "192.168.0.1";
+        ip6 = "fd00::1";
+        extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+          networking.firewall.allowedUDPPorts = [ 23542 ];
+          networking.wireguard.interfaces.wg0 = {
+            ips = [ "10.23.42.1/32" "fc00::1/128" ];
+            listenPort = 23542;
+
+            inherit (wg-snakeoil-keys.peer0) privateKey;
+
+            peers = lib.singleton {
+              allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
+
+              inherit (wg-snakeoil-keys.peer1) publicKey;
+            };
+          };
+        };
+      };
+
+      peer1 = peer {
+        ip4 = "192.168.0.2";
+        ip6 = "fd00::2";
+        extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+          networking.wireguard.interfaces.wg0 = {
+            ips = [ "10.23.42.2/32" "fc00::2/128" ];
+            listenPort = 23542;
+            allowedIPsAsRoutes = false;
+
+            inherit (wg-snakeoil-keys.peer1) privateKey;
+
+            peers = lib.singleton {
+              allowedIPs = [ "0.0.0.0/0" "::/0" ];
+              endpoint = "192.168.0.1:23542";
+              persistentKeepalive = 25;
+
+              inherit (wg-snakeoil-keys.peer0) publicKey;
+            };
+
+            postSetup = let inherit (pkgs) iproute; in ''
+              ${iproute}/bin/ip route replace 10.23.42.1/32 dev wg0
+              ${iproute}/bin/ip route replace fc00::1/128 dev wg0
+            '';
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      peer0.wait_for_unit("wireguard-wg0.service")
+      peer1.wait_for_unit("wireguard-wg0.service")
+
+      peer1.succeed("ping -c5 fc00::1")
+      peer1.succeed("ping -c5 10.23.42.1")
+    '';
+  }
+)
diff --git a/nixos/tests/wireguard/default.nix b/nixos/tests/wireguard/default.nix
index e3bc31c600f..dedb321ff2e 100644
--- a/nixos/tests/wireguard/default.nix
+++ b/nixos/tests/wireguard/default.nix
@@ -1,71 +1,27 @@
-import ../make-test-python.nix ({ pkgs, lib, ...} :
-  let
-    wg-snakeoil-keys = import ./snakeoil-keys.nix;
-    peer = (import ./make-peer.nix) { inherit lib; };
-  in
-  {
-    name = "wireguard";
-    meta = with pkgs.stdenv.lib.maintainers; {
-      maintainers = [ ma27 ];
-    };
-
-    nodes = {
-      peer0 = peer {
-        ip4 = "192.168.0.1";
-        ip6 = "fd00::1";
-        extraConfig = {
-          networking.firewall.allowedUDPPorts = [ 23542 ];
-          networking.wireguard.interfaces.wg0 = {
-            ips = [ "10.23.42.1/32" "fc00::1/128" ];
-            listenPort = 23542;
-
-            inherit (wg-snakeoil-keys.peer0) privateKey;
-
-            peers = lib.singleton {
-              allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
-
-              inherit (wg-snakeoil-keys.peer1) publicKey;
-            };
-          };
-        };
-      };
-
-      peer1 = peer {
-        ip4 = "192.168.0.2";
-        ip6 = "fd00::2";
-        extraConfig = {
-          networking.wireguard.interfaces.wg0 = {
-            ips = [ "10.23.42.2/32" "fc00::2/128" ];
-            listenPort = 23542;
-            allowedIPsAsRoutes = false;
-
-            inherit (wg-snakeoil-keys.peer1) privateKey;
-
-            peers = lib.singleton {
-              allowedIPs = [ "0.0.0.0/0" "::/0" ];
-              endpoint = "192.168.0.1:23542";
-              persistentKeepalive = 25;
-
-              inherit (wg-snakeoil-keys.peer0) publicKey;
-            };
-
-            postSetup = let inherit (pkgs) iproute; in ''
-              ${iproute}/bin/ip route replace 10.23.42.1/32 dev wg0
-              ${iproute}/bin/ip route replace fc00::1/128 dev wg0
-            '';
-          };
-        };
-      };
-    };
-
-    testScript = ''
-      start_all()
-
-      peer0.wait_for_unit("wireguard-wg0.service")
-      peer1.wait_for_unit("wireguard-wg0.service")
-
-      peer1.succeed("ping -c5 fc00::1")
-      peer1.succeed("ping -c5 10.23.42.1")
-    '';
-  }
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+, kernelVersionsToTest ? [ "5.4" "latest" ]
+}:
+
+with pkgs.lib;
+
+let
+  tests = let callTest = p: flip (import p) { inherit system pkgs; }; in {
+    basic = callTest ./basic.nix;
+    namespaces = callTest ./namespaces.nix;
+    wg-quick = callTest ./wg-quick.nix;
+    generated = callTest ./generated.nix;
+  };
+in
+
+listToAttrs (
+  flip concatMap kernelVersionsToTest (version:
+    let
+      v' = replaceStrings [ "." ] [ "_" ] version;
+    in
+    flip mapAttrsToList tests (name: test:
+      nameValuePair "wireguard-${name}-linux-${v'}" (test { kernelPackages = pkgs."linuxPackages_${v'}"; })
+    )
+  )
 )
diff --git a/nixos/tests/wireguard/generated.nix b/nixos/tests/wireguard/generated.nix
index a29afd2d466..cdf15483265 100644
--- a/nixos/tests/wireguard/generated.nix
+++ b/nixos/tests/wireguard/generated.nix
@@ -1,4 +1,5 @@
-import ../make-test-python.nix ({ pkgs, ...} : {
+{ kernelPackages ? null }:
+import ../make-test-python.nix ({ pkgs, lib, ... } : {
   name = "wireguard-generated";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ ma27 grahamc ];
@@ -6,6 +7,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
 
   nodes = {
     peer1 = {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.firewall.allowedUDPPorts = [ 12345 ];
       networking.wireguard.interfaces.wg0 = {
         ips = [ "10.10.10.1/24" ];
@@ -17,6 +19,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     };
 
     peer2 = {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.firewall.allowedUDPPorts = [ 12345 ];
       networking.wireguard.interfaces.wg0 = {
         ips = [ "10.10.10.2/24" ];
diff --git a/nixos/tests/wireguard/namespaces.nix b/nixos/tests/wireguard/namespaces.nix
index c8a4e3bb52a..c47175ceafc 100644
--- a/nixos/tests/wireguard/namespaces.nix
+++ b/nixos/tests/wireguard/namespaces.nix
@@ -1,3 +1,5 @@
+{ kernelPackages ? null }:
+
 let
   listenPort = 12345;
   socketNamespace = "foo";
@@ -13,7 +15,7 @@ let
 
 in
 
-import ../make-test-python.nix ({ pkgs, ...} : {
+import ../make-test-python.nix ({ pkgs, lib, ... } : {
   name = "wireguard-with-namespaces";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ asymmetric ];
@@ -23,6 +25,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     # interface should be created in the socketNamespace
     # and not moved from there
     peer0 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.wireguard.interfaces.wg0 = {
         preSetup = ''
           ip netns add ${socketNamespace}
@@ -33,6 +36,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     # interface should be created in the init namespace
     # and moved to the interfaceNamespace
     peer1 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.wireguard.interfaces.wg0 = {
         preSetup = ''
           ip netns add ${interfaceNamespace}
@@ -43,6 +47,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     # interface should be created in the socketNamespace
     # and moved to the interfaceNamespace
     peer2 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.wireguard.interfaces.wg0 = {
         preSetup = ''
           ip netns add ${socketNamespace}
@@ -54,6 +59,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     # interface should be created in the socketNamespace
     # and moved to the init namespace
     peer3 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
       networking.wireguard.interfaces.wg0 = {
         preSetup = ''
           ip netns add ${socketNamespace}
diff --git a/nixos/tests/wireguard/wg-quick.nix b/nixos/tests/wireguard/wg-quick.nix
index 7354dd01a34..5472d21cd1e 100644
--- a/nixos/tests/wireguard/wg-quick.nix
+++ b/nixos/tests/wireguard/wg-quick.nix
@@ -1,3 +1,5 @@
+{ kernelPackages ? null }:
+
 import ../make-test-python.nix ({ pkgs, lib, ... }:
   let
     wg-snakeoil-keys = import ./snakeoil-keys.nix;
@@ -14,6 +16,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
         ip4 = "192.168.0.1";
         ip6 = "fd00::1";
         extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
           networking.firewall.allowedUDPPorts = [ 23542 ];
           networking.wg-quick.interfaces.wg0 = {
             address = [ "10.23.42.1/32" "fc00::1/128" ];
@@ -34,6 +37,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
         ip4 = "192.168.0.2";
         ip6 = "fd00::2";
         extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
           networking.wg-quick.interfaces.wg0 = {
             address = [ "10.23.42.2/32" "fc00::2/128" ];
             inherit (wg-snakeoil-keys.peer1) privateKey;
diff --git a/nixos/tests/xandikos.nix b/nixos/tests/xandikos.nix
index 0fded20ff1a..48c770a3d16 100644
--- a/nixos/tests/xandikos.nix
+++ b/nixos/tests/xandikos.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix (
     {
       name = "xandikos";
 
-      meta.maintainers = [ lib.maintainers."0x4A6F" ];
+      meta.maintainers = with lib.maintainers; [ _0x4A6F ];
 
       nodes = {
         xandikos_client = {};
@@ -17,7 +17,7 @@ import ./make-test-python.nix (
           services.xandikos.enable = true;
           services.xandikos.address = "localhost";
           services.xandikos.port = 8080;
-          services.xandikos.routePrefix = "/xandikos/";
+          services.xandikos.routePrefix = "/xandikos-prefix/";
           services.xandikos.extraOptions = [
             "--defaults"
           ];
@@ -28,7 +28,7 @@ import ./make-test-python.nix (
               serverName = "xandikos.local";
               basicAuth.xandikos = "snakeOilPassword";
               locations."/xandikos/" = {
-                proxyPass = "http://localhost:8080/";
+                proxyPass = "http://localhost:8080/xandikos-prefix/";
               };
             };
           };
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 99065669661..99e30342e59 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -11,8 +11,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
       services.xserver.enable = true;
 
-      services.xserver.displayManager.lightdm = {
-        enable = true;
+      services.xserver.displayManager = {
+        lightdm.enable = true;
         autoLogin = {
           enable = true;
           user = "alice";
diff --git a/nixos/tests/yggdrasil.nix b/nixos/tests/yggdrasil.nix
index 468fcf67127..1d7541308b4 100644
--- a/nixos/tests/yggdrasil.nix
+++ b/nixos/tests/yggdrasil.nix
@@ -7,6 +7,7 @@ let
     SigningPrivateKey = "fe3add8da35316c05f6d90d3ca79bd2801e6ccab6d37e5339fef4152589398abe2c43349083bc1e998e4ec4535b4c6a8f44ca9a5a8e07336561267253b2be5f4";
   };
   bobIp6 = "201:ebbd:bde9:f138:c302:4afa:1fb6:a19a";
+  bobPrefix = "301:ebbd:bde9:f138";
   bobConfig = {
     InterfacePeers = {
       eth1 = [ "tcp://192.168.1.200:12345" ];
@@ -18,6 +19,7 @@ let
     SigningPublicKey = "de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
     SigningPrivateKey = "2a6c21550f3fca0331df50668ffab66b6dce8237bcd5728e571e8033b363e247de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
   };
+  danIp6 = bobPrefix + "::2";
 
 in import ./make-test-python.nix ({ pkgs, ...} : {
   name = "yggdrasil";
@@ -69,6 +71,41 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
                          text = builtins.toJSON bobConfig;
                        });
         };
+
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
+
+        networking = {
+          bridges.br0.interfaces = [ ];
+          interfaces.br0 = {
+            ipv6.addresses = [{
+              address = bobPrefix + "::1";
+              prefixLength = 64;
+            }];
+          };
+        };
+
+        # dan is a node inside a container running on bob's host.
+        containers.dan = {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config = { config, pkgs, ... }: {
+            networking.interfaces.eth0.ipv6 = {
+              addresses = [{
+                address = bobPrefix + "::2";
+                prefixLength = 64;
+              }];
+              routes = [{
+                address = "200::";
+                prefixLength = 7;
+                via = bobPrefix + "::1";
+              }];
+            };
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.org";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+          };
+        };
       };
 
     # Carol only does local peering.  Carol's yggdrasil config is all Nix.
@@ -85,6 +122,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
             MulticastInterfaces = [ "eth1" ];
             LinkLocalTCPPort = 43210;
           };
+          persistentKeys = true;
         };
       };
     };
@@ -99,7 +137,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
 
       bob.start()
       carol.start()
-      bob.wait_for_unit("yggdrasil.service")
+      bob.wait_for_unit("default.target")
       carol.wait_for_unit("yggdrasil.service")
 
       ip_addr_show = "ip -o -6 addr show dev ygg0 scope global"
@@ -116,10 +154,13 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
 
       carol.succeed("ping -c 1 ${aliceIp6}")
       carol.succeed("ping -c 1 ${bobIp6}")
+      carol.succeed("ping -c 1 ${bobPrefix}::1")
+      carol.succeed("ping -c 8 ${danIp6}")
 
       carol.fail("journalctl -u dhcpcd | grep ygg0")
 
       alice.wait_for_unit("httpd.service")
       carol.succeed("curl --fail -g http://[${aliceIp6}]")
+      carol.succeed("curl --fail -g http://[${danIp6}]")
     '';
 })
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 7ba60ee9806..87e6c900c98 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -46,6 +46,17 @@ let
             "zpool destroy rpool",
             "udevadm settle",
         )
+
+        machine.succeed(
+            'echo password | zpool create -o altroot="/tmp/mnt" '
+            + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
+            "zfs create -o mountpoint=legacy rpool/root",
+            "mount -t zfs rpool/root /tmp/mnt",
+            "udevadm settle",
+            "umount /tmp/mnt",
+            "zpool destroy rpool",
+            "udevadm settle",
+        )
       '' + extraTest;
 
     };
@@ -57,18 +68,6 @@ in {
 
   unstable = makeZfsTest "unstable" {
     enableUnstable = true;
-    extraTest = ''
-      machine.succeed(
-          'echo password | zpool create -o altroot="/tmp/mnt" '
-          + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
-          "zfs create -o mountpoint=legacy rpool/root",
-          "mount -t zfs rpool/root /tmp/mnt",
-          "udevadm settle",
-          "umount /tmp/mnt",
-          "zpool destroy rpool",
-          "udevadm settle",
-      )
-    '';
   };
 
   installer = (import ./installer.nix { }).zfsroot;
diff --git a/nixos/tests/zigbee2mqtt.nix b/nixos/tests/zigbee2mqtt.nix
new file mode 100644
index 00000000000..b7bb21f9227
--- /dev/null
+++ b/nixos/tests/zigbee2mqtt.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  {
+    machine = { pkgs, ... }:
+      {
+        services.zigbee2mqtt = {
+          enable = true;
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("zigbee2mqtt.service")
+      machine.wait_until_fails("systemctl status zigbee2mqtt.service")
+      machine.succeed(
+          "journalctl -eu zigbee2mqtt | grep \"Error: Error while opening serialport 'Error: Error: No such file or directory, cannot open /dev/ttyACM0'\""
+      )
+    '';
+  }
+)
diff --git a/nixos/tests/zoneminder.nix b/nixos/tests/zoneminder.nix
new file mode 100644
index 00000000000..a4e1a05ec0e
--- /dev/null
+++ b/nixos/tests/zoneminder.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ...}:
+
+{
+  name = "zoneminder";
+  meta.maintainers = with lib.maintainers; [ danielfullmer ];
+
+  machine = { ... }:
+  {
+    services.zoneminder = {
+      enable = true;
+      database.createLocally = true;
+      database.username = "zoneminder";
+    };
+    time.timeZone = "America/New_York";
+  };
+
+  testScript = ''
+    machine.wait_for_unit("zoneminder.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(8095)
+    machine.succeed("curl --fail http://localhost:8095/")
+  '';
+})