about 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.nix54
-rw-r--r--nixos/tests/agda.nix3
-rw-r--r--nixos/tests/all-tests.nix8
-rw-r--r--nixos/tests/bitwarden.nix1
-rw-r--r--nixos/tests/calibre-web.nix53
-rw-r--r--nixos/tests/croc.nix2
-rw-r--r--nixos/tests/docker-tools.nix45
-rw-r--r--nixos/tests/dokuwiki.nix4
-rw-r--r--nixos/tests/elk.nix1
-rw-r--r--nixos/tests/etebase-server.nix50
-rw-r--r--nixos/tests/gitlab.nix135
-rw-r--r--nixos/tests/hledger-web.nix25
-rw-r--r--nixos/tests/inspircd.nix93
-rw-r--r--nixos/tests/matrix-appservice-irc.nix162
-rw-r--r--nixos/tests/mysql/mariadb-galera-mariabackup.nix2
-rw-r--r--nixos/tests/ombi.nix18
-rw-r--r--nixos/tests/podman.nix9
-rw-r--r--nixos/tests/pomerium.nix102
-rw-r--r--nixos/tests/privacyidea.nix12
-rw-r--r--nixos/tests/prometheus-exporters.nix94
-rw-r--r--nixos/tests/restic.nix6
-rw-r--r--nixos/tests/snapcast.nix4
-rw-r--r--nixos/tests/systemd-template-override.nix41
-rw-r--r--nixos/tests/systemd-unit-path.nix47
-rw-r--r--nixos/tests/wiki-js.nix152
-rw-r--r--nixos/tests/wireguard/basic.nix6
26 files changed, 1001 insertions, 128 deletions
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index c6d393d91963..99dd8ec6fd3c 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -253,7 +253,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
 
 
       def check_connection(node, domain, retries=3):
-          assert retries >= 0
+          assert retries >= 0, f"Failed to connect to https://{domain}"
 
           result = node.succeed(
               "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"
@@ -262,12 +262,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
 
           for line in result.lower().split("\n"):
               if "verification" in line and "error" in line:
-                  time.sleep(1)
+                  time.sleep(3)
                   return check_connection(node, domain, retries - 1)
 
 
       def check_connection_key_bits(node, domain, bits, retries=3):
-          assert retries >= 0
+          assert retries >= 0, f"Did not find expected number of bits ({bits}) in key"
 
           result = node.succeed(
               "openssl s_client -CAfile /tmp/ca.crt"
@@ -277,12 +277,12 @@ in import ./make-test-python.nix ({ lib, ... }: {
           print("Key type:", result)
 
           if bits not in result:
-              time.sleep(1)
+              time.sleep(3)
               return check_connection_key_bits(node, domain, bits, retries - 1)
 
 
       def check_stapling(node, domain, retries=3):
-          assert retries >= 0
+          assert retries >= 0, "OCSP Stapling check failed"
 
           # Pebble doesn't provide a full OCSP responder, so just check the URL
           result = node.succeed(
@@ -293,10 +293,23 @@ in import ./make-test-python.nix ({ lib, ... }: {
           print("OCSP Responder URL:", result)
 
           if "${caDomain}:4002" not in result.lower():
-              time.sleep(1)
+              time.sleep(3)
               return check_stapling(node, domain, retries - 1)
 
 
+      def download_ca_certs(node, retries=5):
+          assert retries >= 0, "Failed to connect to pebble to download root CA certs"
+
+          exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
+          exit_code_2, _ = node.execute(
+              "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt"
+          )
+
+          if exit_code + exit_code_2 > 0:
+              time.sleep(3)
+              return download_ca_certs(node, retries - 1)
+
+
       client.start()
       dnsserver.start()
 
@@ -313,8 +326,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
       acme.wait_for_unit("network-online.target")
       acme.wait_for_unit("pebble.service")
 
-      client.succeed("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
-      client.succeed("curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt")
+      download_ca_certs(client)
 
       with subtest("Can request certificate with HTTPS-01 challenge"):
           webserver.wait_for_unit("acme-finished-a.example.test.target")
@@ -322,6 +334,21 @@ in import ./make-test-python.nix ({ lib, ... }: {
           check_issuer(webserver, "a.example.test", "pebble")
           check_connection(client, "a.example.test")
 
+      with subtest("Certificates and accounts have safe + valid permissions"):
+          group = "${nodes.webserver.config.security.acme.certs."a.example.test".group}"
+          webserver.succeed(
+              f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
+          )
+          webserver.succeed(
+              f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/.lego/a.example.test/**/* | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
+          )
+          webserver.succeed(
+              f"test $(stat -L -c \"%a %U %G\" /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1"
+          )
+          webserver.succeed(
+              f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c \"%a %U %G\" {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0"
+          )
+
       with subtest("Can generate valid selfsigned certs"):
           webserver.succeed("systemctl clean acme-a.example.test.service --what=state")
           webserver.succeed("systemctl start acme-selfsigned-a.example.test.service")
@@ -375,8 +402,15 @@ in import ./make-test-python.nix ({ lib, ... }: {
           assert keyhash_old == keyhash_new
 
       with subtest("Can request certificates for vhost + aliases (apache-httpd)"):
-          switch_to(webserver, "httpd-aliases")
-          webserver.wait_for_unit("acme-finished-c.example.test.target")
+          try:
+              switch_to(webserver, "httpd-aliases")
+              webserver.wait_for_unit("acme-finished-c.example.test.target")
+          except Exception as err:
+              _, output = webserver.execute(
+                  "cat /var/log/httpd/*.log && ls -al /var/lib/acme/acme-challenge"
+              )
+              print(output)
+              raise err
           check_issuer(webserver, "c.example.test", "pebble")
           check_connection(client, "c.example.test")
           check_connection(client, "d.example.test")
diff --git a/nixos/tests/agda.nix b/nixos/tests/agda.nix
index 3773907cff55..f282788519c8 100644
--- a/nixos/tests/agda.nix
+++ b/nixos/tests/agda.nix
@@ -3,8 +3,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
 let
   hello-world = pkgs.writeText "hello-world" ''
     open import IO
+    open import Level
 
-    main = run(putStrLn "Hello World!")
+    main = run {0ℓ} (putStrLn "Hello World!")
   '';
 in
 {
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 3ce71b0abe6d..62188ddf9e8d 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -49,6 +49,7 @@ in
   cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
   cage = handleTest ./cage.nix {};
   cagebreak = handleTest ./cagebreak.nix {};
+  calibre-web = handleTest ./calibre-web.nix {};
   cassandra_2_1 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_1; };
   cassandra_2_2 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_2; };
   cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; };
@@ -110,6 +111,7 @@ in
   ergo = handleTest ./ergo.nix {};
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
+  etebase-server = handleTest ./etebase-server.nix {};
   etesync-dav = handleTest ./etesync-dav.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
   fcitx = handleTest ./fcitx {};
@@ -176,6 +178,7 @@ in
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   initrd-secrets = handleTest ./initrd-secrets.nix {};
+  inspircd = handleTest ./inspircd.nix {};
   installer = handleTest ./installer.nix {};
   iodine = handleTest ./iodine.nix {};
   ipfs = handleTest ./ipfs.nix {};
@@ -224,6 +227,7 @@ in
   mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {};
   mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {};
   matomo = handleTest ./matomo.nix {};
+  matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   memcached = handleTest ./memcached.nix {};
@@ -286,6 +290,7 @@ in
   nzbget = handleTest ./nzbget.nix {};
   nzbhydra2 = handleTest ./nzbhydra2.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
+  ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
@@ -317,6 +322,7 @@ in
   plikd = handleTest ./plikd.nix {};
   plotinus = handleTest ./plotinus.nix {};
   podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
+  pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
   postfix = handleTest ./postfix.nix {};
   postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
   postgis = handleTest ./postgis.nix {};
@@ -393,6 +399,7 @@ in
   systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
   systemd-nspawn = handleTest ./systemd-nspawn.nix {};
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
+  systemd-unit-path = handleTest ./systemd-unit-path.nix {};
   taskserver = handleTest ./taskserver.nix {};
   telegraf = handleTest ./telegraf.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
@@ -424,6 +431,7 @@ in
   virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
   vscodium = handleTest ./vscodium.nix {};
   wasabibackend = handleTest ./wasabibackend.nix {};
+  wiki-js = handleTest ./wiki-js.nix {};
   wireguard = handleTest ./wireguard {};
   wordpress = handleTest ./wordpress.nix {};
   xandikos = handleTest ./xandikos.nix {};
diff --git a/nixos/tests/bitwarden.nix b/nixos/tests/bitwarden.nix
index 5345c7245d00..3813a1f70f96 100644
--- a/nixos/tests/bitwarden.nix
+++ b/nixos/tests/bitwarden.nix
@@ -113,6 +113,7 @@ let
                   driver.find_element_by_css_selector('input#masterPasswordRetype').send_keys(
                     '${userPassword}'
                   )
+                  driver.find_element_by_css_selector('input#acceptPolicies').click()
 
                   driver.find_element_by_xpath("//button[contains(., 'Submit')]").click()
 
diff --git a/nixos/tests/calibre-web.nix b/nixos/tests/calibre-web.nix
new file mode 100644
index 000000000000..4f73b3311124
--- /dev/null
+++ b/nixos/tests/calibre-web.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+    let
+      port = 3142;
+      defaultPort = 8083;
+    in
+      with lib;
+      {
+        name = "calibre-web";
+        meta.maintainers = with pkgs.lib.maintainers; [ pborzenkov ];
+
+        nodes = {
+          default = { ... }: {
+            services.calibre-web.enable = true;
+          };
+
+          customized = { pkgs, ... }: {
+            services.calibre-web = {
+              enable = true;
+              listen.port = port;
+              options = {
+                calibreLibrary = "/tmp/books";
+                reverseProxyAuth = {
+                  enable = true;
+                  header = "X-User";
+                };
+              };
+            };
+            environment.systemPackages = [ pkgs.calibre ];
+          };
+        };
+        testScript = ''
+          start_all()
+
+          default.wait_for_unit("calibre-web.service")
+          default.wait_for_open_port(${toString defaultPort})
+          default.succeed(
+              "curl --fail 'http://localhost:${toString defaultPort}/basicconfig' | grep -q 'Basic Configuration'"
+          )
+
+          customized.succeed(
+              "mkdir /tmp/books && calibredb --library-path /tmp/books add -e --title test-book"
+          )
+          customized.succeed("systemctl restart calibre-web")
+          customized.wait_for_unit("calibre-web.service")
+          customized.wait_for_open_port(${toString port})
+          customized.succeed(
+              "curl --fail -H X-User:admin 'http://localhost:${toString port}' | grep -q test-book"
+          )
+        '';
+      }
+)
diff --git a/nixos/tests/croc.nix b/nixos/tests/croc.nix
index c1b6fc7232d7..75a8fc991d47 100644
--- a/nixos/tests/croc.nix
+++ b/nixos/tests/croc.nix
@@ -6,7 +6,7 @@ let
   pass = pkgs.writeText "pass" "PassRelay";
 in {
   name = "croc";
-  meta = with pkgs.stdenv.lib.maintainers; {
+  meta = with pkgs.lib.maintainers; {
     maintainers = [ hax404 julm ];
   };
 
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index a71cf79c5a78..80d527b453fa 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -161,12 +161,18 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run --rm ${examples.layered-image.imageName} cat extraCommands",
         )
 
-    with subtest("Ensure building an image on top of a layered Docker images work"):
+    with subtest("Ensure images built on top of layered Docker images work"):
         docker.succeed(
             "docker load --input='${examples.layered-on-top}'",
             "docker run --rm ${examples.layered-on-top.imageName}",
         )
 
+    with subtest("Ensure layered images built on top of layered Docker images work"):
+        docker.succeed(
+            "docker load --input='${examples.layered-on-top-layered}'",
+            "docker run --rm ${examples.layered-on-top-layered.imageName}",
+        )
+
 
     def set_of_layers(image_name):
         return set(
@@ -205,6 +211,31 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         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 environment variables of layered images are correctly inherited"):
+        docker.succeed(
+            "docker load --input='${examples.environmentVariablesLayered}'"
+        )
+        out = docker.succeed("docker run --rm ${examples.environmentVariablesLayered.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 inherited environment variables of layered images are correctly resolved"
+    ):
+        # Read environment variables as stored in image config
+        config = docker.succeed(
+            "tar -xOf ${examples.environmentVariablesLayered} manifest.json | ${pkgs.jq}/bin/jq -r .[].Config"
+        ).strip()
+        out = docker.succeed(
+            f"tar -xOf ${examples.environmentVariablesLayered} {config} | ${pkgs.jq}/bin/jq -r '.config.Env | .[]'"
+        )
+        env = out.splitlines()
+        assert (
+            sum(entry.startswith("LAST_LAYER") for entry in env) == 1
+        ), "envvars overridden by child should be unique"
+
     with subtest("Ensure image with only 2 layers can be loaded"):
         docker.succeed(
             "docker load --input='${examples.two-layered-image}'"
@@ -219,6 +250,18 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run bulk-layer ls /bin/hello",
         )
 
+    with subtest(
+        "Ensure the bulk layer with a base image respects the number of maxLayers"
+    ):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.layered-bulk-layer}'",
+            # Ensure the image runs correctly
+            "docker run layered-bulk-layer ls /bin/hello",
+        )
+
+        # Ensure the image has the correct number of layers
+        assert len(set_of_layers("layered-bulk-layer")) == 4
+
     with subtest("Ensure correct behavior when no store is needed"):
         # This check tests that buildLayeredImage can build images that don't need a store.
         docker.succeed(
diff --git a/nixos/tests/dokuwiki.nix b/nixos/tests/dokuwiki.nix
index 40475d789d47..2664e1500ea4 100644
--- a/nixos/tests/dokuwiki.nix
+++ b/nixos/tests/dokuwiki.nix
@@ -9,7 +9,7 @@ let
       sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
     };
     # We need unzip to build this package
-    buildInputs = [ pkgs.unzip ];
+    nativeBuildInputs = [ pkgs.unzip ];
     # Installing simply means copying all files to the output directory
     installPhase = "mkdir -p $out; cp -R * $out/";
   };
@@ -24,7 +24,7 @@ let
       sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
     };
     # We need unzip to build this package
-    buildInputs = [ pkgs.unzip ];
+    nativeBuildInputs = [ pkgs.unzip ];
     sourceRoot = ".";
     # Installing simply means copying all files to the output directory
     installPhase = "mkdir -p $out; cp -R * $out/";
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
index 8488c97c01e8..fee350de65b5 100644
--- a/nixos/tests/elk.nix
+++ b/nixos/tests/elk.nix
@@ -120,6 +120,7 @@ let
           };
       };
 
+    passthru.elkPackages = elk;
     testScript = ''
       import json
 
diff --git a/nixos/tests/etebase-server.nix b/nixos/tests/etebase-server.nix
new file mode 100644
index 000000000000..4fc3c1f6392f
--- /dev/null
+++ b/nixos/tests/etebase-server.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  dataDir = "/var/lib/foobar";
+
+in {
+    name = "etebase-server";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ felschr ];
+    };
+
+    machine = { pkgs, ... }:
+      {
+        services.etebase-server = {
+          inherit dataDir;
+          enable = true;
+          settings.global.secret_file =
+            toString (pkgs.writeText "secret" "123456");
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("etebase-server.service")
+      machine.wait_for_open_port(8001)
+
+      with subtest("Database & src-version were created"):
+          machine.wait_for_file("${dataDir}/src-version")
+          assert (
+              "${pkgs.etebase-server}"
+              in machine.succeed("cat ${dataDir}/src-version")
+          )
+          machine.wait_for_file("${dataDir}/db.sqlite3")
+          machine.wait_for_file("${dataDir}/static")
+
+      with subtest("Only allow access from allowed_hosts"):
+          machine.succeed("curl -sSfL http://0.0.0.0:8001/")
+          machine.fail("curl -sSfL http://127.0.0.1:8001/")
+          machine.fail("curl -sSfL http://localhost:8001/")
+
+      with subtest("Run tests"):
+          machine.succeed("etebase-server check")
+          machine.succeed("etebase-server test")
+
+      with subtest("Create superuser"):
+          machine.succeed(
+              "etebase-server createsuperuser --no-input --username admin --email root@localhost"
+          )
+    '';
+  }
+)
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index baad675229cc..582f5faf9bc0 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -34,6 +34,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
         enableImap = true;
       };
 
+      systemd.services.gitlab-backup.environment.BACKUP = "dump";
+
       services.gitlab = {
         enable = true;
         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
@@ -64,60 +66,89 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
     };
   };
 
-  testScript =
-  let
-    auth = pkgs.writeText "auth.json" (builtins.toJSON {
-      grant_type = "password";
-      username = "root";
-      password = initialRootPassword;
-    });
+  testScript = { nodes, ... }:
+    let
+      auth = pkgs.writeText "auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = "root";
+        password = initialRootPassword;
+      });
+
+      createProject = pkgs.writeText "create-project.json" (builtins.toJSON {
+        name = "test";
+      });
 
-    createProject = pkgs.writeText "create-project.json" (builtins.toJSON {
-      name = "test";
-    });
+      putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
+        branch = "master";
+        author_email = "author@example.com";
+        author_name = "Firstname Lastname";
+        content = "some content";
+        commit_message = "create a new file";
+      });
 
-    putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
-      branch = "master";
-      author_email = "author@example.com";
-      author_name = "Firstname Lastname";
-      content = "some content";
-      commit_message = "create a new file";
-    });
-  in
-  ''
-    gitlab.start()
+      # Wait for all GitLab services to be fully started.
+      waitForServices = ''
+        gitlab.wait_for_unit("gitaly.service")
+        gitlab.wait_for_unit("gitlab-workhorse.service")
+        gitlab.wait_for_unit("gitlab-pages.service")
+        gitlab.wait_for_unit("gitlab-mailroom.service")
+        gitlab.wait_for_unit("gitlab.service")
+        gitlab.wait_for_unit("gitlab-sidekiq.service")
+        gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
+        gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
+      '';
 
-    gitlab.wait_for_unit("gitaly.service")
-    gitlab.wait_for_unit("gitlab-workhorse.service")
-    gitlab.wait_for_unit("gitlab-pages.service")
-    gitlab.wait_for_unit("gitlab-mailroom.service")
-    gitlab.wait_for_unit("gitlab.service")
-    gitlab.wait_for_unit("gitlab-sidekiq.service")
-    gitlab.wait_for_file("/var/gitlab/state/tmp/sockets/gitlab.socket")
-    gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
+      # The actual test of GitLab. Only push data to GitLab if
+      # `doSetup` is is true.
+      test = doSetup: ''
+        gitlab.succeed(
+            "curl -isSf http://gitlab | grep -i location | grep -q http://gitlab/users/sign_in"
+        )
+        gitlab.succeed(
+            "${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2"
+        )
+        gitlab.succeed(
+            "echo \"Authorization: Bearer \$(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
+        )
+      '' + optionalString doSetup ''
+        gitlab.succeed(
+            "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createProject} http://gitlab/api/v4/projects"
+        )
+        gitlab.succeed(
+            "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${putFile} http://gitlab/api/v4/projects/1/repository/files/some-file.txt"
+        )
+      '' + ''
+        gitlab.succeed(
+            "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.gz > /tmp/archive.tar.gz"
+        )
+        gitlab.succeed(
+            "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.bz2 > /tmp/archive.tar.bz2"
+        )
+        gitlab.succeed("test -s /tmp/archive.tar.gz")
+        gitlab.succeed("test -s /tmp/archive.tar.bz2")
+      '';
 
-    gitlab.succeed(
-        "curl -isSf http://gitlab | grep -i location | grep -q http://gitlab/users/sign_in"
-    )
-    gitlab.succeed(
-        "${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2"
-    )
-    gitlab.succeed(
-        "echo \"Authorization: Bearer \$(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
-    )
-    gitlab.succeed(
-        "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createProject} http://gitlab/api/v4/projects"
-    )
-    gitlab.succeed(
-        "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${putFile} http://gitlab/api/v4/projects/1/repository/files/some-file.txt"
-    )
-    gitlab.succeed(
-        "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.gz > /tmp/archive.tar.gz"
-    )
-    gitlab.succeed(
-        "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.bz2 > /tmp/archive.tar.bz2"
-    )
-    gitlab.succeed("test -s /tmp/archive.tar.gz")
-    gitlab.succeed("test -s /tmp/archive.tar.bz2")
-  '';
+  in ''
+      gitlab.start()
+    ''
+    + waitForServices
+    + test true
+    + ''
+      gitlab.systemctl("start gitlab-backup.service")
+      gitlab.wait_for_unit("gitlab-backup.service")
+      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
+      gitlab.systemctl("stop postgresql.service gitlab.target")
+      gitlab.succeed(
+          "find ${nodes.gitlab.config.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
+      )
+      gitlab.succeed("systemd-tmpfiles --create")
+      gitlab.succeed("rm -rf ${nodes.gitlab.config.services.postgresql.dataDir}")
+      gitlab.systemctl("start gitlab-config.service gitlab-postgresql.service")
+      gitlab.succeed(
+          "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
+      )
+      gitlab.systemctl("start gitlab.target")
+    ''
+    + waitForServices
+    + test false;
 })
diff --git a/nixos/tests/hledger-web.nix b/nixos/tests/hledger-web.nix
index 378d819437db..f8919f7d4bd0 100644
--- a/nixos/tests/hledger-web.nix
+++ b/nixos/tests/hledger-web.nix
@@ -13,25 +13,22 @@ rec {
   name = "hledger-web";
   meta.maintainers = with lib.maintainers; [ marijanp ];
 
-  nodes = {
-    server = { config, pkgs, ... }: rec {
+  nodes = rec {
+    server = { config, pkgs, ... }: {
       services.hledger-web = {
         host = "127.0.0.1";
         port = 5000;
         enable = true;
-        journalFile = journal;
+        capabilities.manage = true;
       };
-      networking.firewall.allowedTCPPorts = [ services.hledger-web.port ];
+      networking.firewall.allowedTCPPorts = [ config.services.hledger-web.port ];
+      systemd.services.hledger-web.preStart = ''
+        ln -s ${journal} /var/lib/hledger-web/.hledger.journal
+      '';
     };
-    apiserver = { config, pkgs, ... }: rec {
-      services.hledger-web = {
-        host = "127.0.0.1";
-        port = 5000;
-        enable = true;
-        serveApi = true;
-        journalFile = journal;
-      };
-      networking.firewall.allowedTCPPorts = [ services.hledger-web.port ];
+    apiserver = { ... }: {
+      imports = [ server ];
+      services.hledger-web.serveApi = true;
     };
   };
 
@@ -42,7 +39,7 @@ rec {
     server.wait_for_open_port(5000)
     with subtest("Check if web UI is accessible"):
         page = server.succeed("curl -L http://127.0.0.1:5000")
-        assert "test.journal" in page
+        assert ".hledger.journal" in page
 
     apiserver.wait_for_unit("hledger-web.service")
     apiserver.wait_for_open_port(5000)
diff --git a/nixos/tests/inspircd.nix b/nixos/tests/inspircd.nix
new file mode 100644
index 000000000000..f4d82054011c
--- /dev/null
+++ b/nixos/tests/inspircd.nix
@@ -0,0 +1,93 @@
+let
+  clients = [
+    "ircclient1"
+    "ircclient2"
+  ];
+  server = "inspircd";
+  ircPort = 6667;
+  channel = "nixos-cat";
+  iiDir = "/tmp/irc";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "inspircd";
+  nodes = {
+    "${server}" = {
+      networking.firewall.allowedTCPPorts = [ ircPort ];
+      services.inspircd = {
+        enable = true;
+        package = pkgs.inspircdMinimal;
+        config = ''
+          <bind address="" port="${toString ircPort}" type="clients">
+          <connect name="main" allow="*" pingfreq="15">
+        '';
+      };
+    };
+  } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    systemd.services.ii = {
+      requires = [ "network.target" ];
+      wantedBy = [ "default.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecPreStartPre = "mkdir -p ${iiDir}";
+        ExecStart = ''
+          ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir}
+        '';
+        User = "alice";
+      };
+    };
+  }) clients);
+
+  testScript =
+    let
+      msg = client: "Hello, my name is ${client}";
+      clientScript = client: [
+        ''
+          ${client}.wait_for_unit("network.target")
+          ${client}.systemctl("start ii")
+          ${client}.wait_for_unit("ii")
+          ${client}.wait_for_file("${iiDir}/${server}/out")
+        ''
+        # wait until first PING from server arrives before joining,
+        # so we don't try it too early
+        ''
+          ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out")
+        ''
+        # join ${channel}
+        ''
+          ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in")
+          ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in")
+        ''
+        # send a greeting
+        ''
+          ${client}.succeed(
+              "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in"
+          )
+        ''
+        # check that all greetings arrived on all clients
+      ] ++ builtins.map (other: ''
+        ${client}.succeed(
+            "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out"
+        )
+      '') clients;
+
+      # foldl', but requires a non-empty list instead of a start value
+      reduce = f: list:
+        builtins.foldl' f (builtins.head list) (builtins.tail list);
+    in ''
+      start_all()
+      ${server}.wait_for_open_port(${toString ircPort})
+
+      # run clientScript for all clients so that every list
+      # entry is executed by every client before advancing
+      # to the next one.
+    '' + lib.concatStrings
+      (reduce
+        (lib.zipListsWith (cs: c: cs + c))
+        (builtins.map clientScript clients));
+})
diff --git a/nixos/tests/matrix-appservice-irc.nix b/nixos/tests/matrix-appservice-irc.nix
new file mode 100644
index 000000000000..79b07ef83c57
--- /dev/null
+++ b/nixos/tests/matrix-appservice-irc.nix
@@ -0,0 +1,162 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    homeserverUrl = "http://homeserver:8448";
+  in
+  {
+    name = "matrix-appservice-irc";
+    meta = {
+      maintainers = pkgs.matrix-appservice-irc.meta.maintainers;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        # We'll switch to this once the config is copied into place
+        specialisation.running.configuration = {
+          services.matrix-synapse = {
+            enable = true;
+            database_type = "sqlite3";
+            app_service_config_files = [ "/registration.yml" ];
+
+            enable_registration = true;
+
+            listeners = [
+              # The default but tls=false
+              {
+                "bind_address" = "";
+                "port" = 8448;
+                "resources" = [
+                  { "compress" = true; "names" = [ "client" "webclient" ]; }
+                  { "compress" = false; "names" = [ "federation" ]; }
+                ];
+                "tls" = false;
+                "type" = "http";
+                "x_forwarded" = false;
+              }
+            ];
+          };
+
+          networking.firewall.allowedTCPPorts = [ 8448 ];
+        };
+      };
+
+      ircd = { pkgs, ... }: {
+        services.ngircd = {
+          enable = true;
+          config = ''
+            [Global]
+              Name = ircd.ircd
+              Info = Server Info Text
+              AdminInfo1 = _
+
+            [Channel]
+              Name = #test
+              Topic = a cool place
+
+            [Options]
+              PAM = no
+          '';
+        };
+        networking.firewall.allowedTCPPorts = [ 6667 ];
+      };
+
+      appservice = { pkgs, ... }: {
+        services.matrix-appservice-irc = {
+          enable = true;
+          registrationUrl = "http://appservice:8009";
+
+          settings = {
+            homeserver.url = homeserverUrl;
+            homeserver.domain = "homeserver";
+
+            ircService.servers."ircd" = {
+              name = "IRCd";
+              port = 6667;
+              dynamicChannels = {
+                enabled = true;
+                aliasTemplate = "#irc_$CHANNEL";
+              };
+            };
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8009 ];
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "do_test"
+            { libraries = [ pkgs.python3Packages.matrix-client ]; } ''
+            import socket
+            from matrix_client.client import MatrixClient
+            from time import sleep
+
+            matrix = MatrixClient("${homeserverUrl}")
+            matrix.register_with_password(username="alice", password="foobar")
+
+            irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            irc.connect(("ircd", 6667))
+            irc.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+            irc.send(b"USER bob bob bob :bob\n")
+            irc.send(b"NICK bob\n")
+
+            m_room = matrix.join_room("#irc_#test:homeserver")
+            irc.send(b"JOIN #test\n")
+
+            # plenty of time for the joins to happen
+            sleep(10)
+
+            m_room.send_text("hi from matrix")
+            irc.send(b"PRIVMSG #test :hi from irc \r\n")
+
+            print("Waiting for irc message...")
+            while True:
+                buf = irc.recv(10000)
+                if b"hi from matrix" in buf:
+                    break
+
+            print("Waiting for matrix message...")
+
+
+            def callback(room, e):
+                if "hi from irc" in e['content']['body']:
+                    exit(0)
+
+
+            m_room.add_listener(callback, "m.room.message")
+            matrix.listen_forever()
+          ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ircd.wait_for_unit("ngircd.service")
+      ircd.wait_for_open_port(6667)
+
+      with subtest("start the appservice"):
+          appservice.wait_for_unit("matrix-appservice-irc.service")
+          appservice.wait_for_open_port(8009)
+
+      with subtest("copy the registration file"):
+          appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
+          homeserver.copy_from_host(
+              pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml", "/"
+          )
+          homeserver.succeed("chmod 444 /registration.yml")
+
+      with subtest("start the homeserver"):
+          homeserver.succeed(
+              "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2"
+          )
+
+          homeserver.wait_for_unit("matrix-synapse.service")
+          homeserver.wait_for_open_port(8448)
+
+      with subtest("ensure messages can be exchanged"):
+          client.succeed("do_test")
+    '';
+
+  })
diff --git a/nixos/tests/mysql/mariadb-galera-mariabackup.nix b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
index a4b893a9f33a..0a40c010a471 100644
--- a/nixos/tests/mysql/mariadb-galera-mariabackup.nix
+++ b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
@@ -2,7 +2,7 @@ import ./../make-test-python.nix ({ pkgs, ...} :
 
 let
   mysqlenv-common      = pkgs.buildEnv { name = "mysql-path-env-common";      pathsToLink = [ "/bin" ]; paths = with pkgs; [ bash gawk gnutar inetutils which ]; };
-  mysqlenv-mariabackup = pkgs.buildEnv { name = "mysql-path-env-mariabackup"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ gzip iproute netcat procps pv socat ]; };
+  mysqlenv-mariabackup = pkgs.buildEnv { name = "mysql-path-env-mariabackup"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ gzip iproute2 netcat procps pv socat ]; };
 
 in {
   name = "mariadb-galera-mariabackup";
diff --git a/nixos/tests/ombi.nix b/nixos/tests/ombi.nix
new file mode 100644
index 000000000000..bfca86af8175
--- /dev/null
+++ b/nixos/tests/ombi.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "ombi";
+  meta.maintainers = with maintainers; [ woky ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.ombi.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("ombi.service")
+    machine.wait_for_open_port("5000")
+    machine.succeed("curl --fail http://localhost:5000/")
+  '';
+})
diff --git a/nixos/tests/podman.nix b/nixos/tests/podman.nix
index 4985ff60365c..6078a936edea 100644
--- a/nixos/tests/podman.nix
+++ b/nixos/tests/podman.nix
@@ -96,6 +96,15 @@ import ./make-test-python.nix (
           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 with init"):
+          podman.succeed(
+              "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox"
+          )
+          pid = podman.succeed("podman run --rm busybox readlink /proc/self").strip()
+          assert pid == "1"
+          pid = podman.succeed("podman run --rm --init busybox readlink /proc/self").strip()
+          assert pid == "2"
     '';
   }
 )
diff --git a/nixos/tests/pomerium.nix b/nixos/tests/pomerium.nix
new file mode 100644
index 000000000000..531b6212711f
--- /dev/null
+++ b/nixos/tests/pomerium.nix
@@ -0,0 +1,102 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pomerium";
+  meta = with lib.maintainers; {
+    maintainers = [ lukegb ];
+  };
+
+  nodes = let base = myIP: { pkgs, lib, ... }: {
+    virtualisation.vlans = [ 1 ];
+    networking = {
+      dhcpcd.enable = false;
+      firewall.allowedTCPPorts = [ 80 443 ];
+      hosts = {
+        "192.168.1.1" = [ "pomerium" "pom-auth" ];
+        "192.168.1.2" = [ "backend" "dummy-oidc" ];
+      };
+      interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+        { address = myIP; prefixLength = 24; }
+      ];
+    };
+  }; in {
+    pomerium = { pkgs, lib, ... }: {
+      imports = [ (base "192.168.1.1") ];
+      services.pomerium = {
+        enable = true;
+        settings = {
+          address = ":80";
+          insecure_server = true;
+          authenticate_service_url = "http://pom-auth";
+
+          idp_provider = "oidc";
+          idp_scopes = [ "oidc" ];
+          idp_client_id = "dummy";
+          idp_provider_url = "http://dummy-oidc";
+
+          policy = [{
+            from = "https://my.website";
+            to = "http://192.168.1.2";
+            allow_public_unauthenticated_access = true;
+            preserve_host_header = true;
+          } {
+            from = "https://login.required";
+            to = "http://192.168.1.2";
+            allowed_domains = [ "my.domain" ];
+            preserve_host_header = true;
+          }];
+        };
+        secretsFile = pkgs.writeText "pomerium-secrets" ''
+          # 12345678901234567890123456789012 in base64
+          COOKIE_SECRET=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=
+          IDP_CLIENT_SECRET=dummy
+        '';
+      };
+    };
+    backend = { pkgs, lib, ... }: {
+      imports = [ (base "192.168.1.2") ];
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."my.website" = {
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir "$out"
+          echo hello world > "$out/index.html"
+        '';
+      };
+      services.nginx.virtualHosts."dummy-oidc" = {
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir -p "$out/.well-known"
+          cat <<EOF >"$out/.well-known/openid-configuration"
+            {
+              "issuer": "http://dummy-oidc",
+              "authorization_endpoint": "http://dummy-oidc/auth.txt",
+              "token_endpoint": "http://dummy-oidc/token",
+              "jwks_uri": "http://dummy-oidc/jwks.json",
+              "userinfo_endpoint": "http://dummy-oidc/userinfo",
+              "id_token_signing_alg_values_supported": ["RS256"]
+            }
+          EOF
+          echo hello I am login page >"$out/auth.txt"
+        '';
+      };
+    };
+  };
+
+  testScript = { ... }: ''
+    backend.wait_for_unit("nginx")
+    backend.wait_for_open_port(80)
+
+    pomerium.wait_for_unit("pomerium")
+    pomerium.wait_for_open_port(80)
+
+    with subtest("no authentication required"):
+        pomerium.succeed(
+            "curl --resolve my.website:80:127.0.0.1 http://my.website | grep -q 'hello world'"
+        )
+
+    with subtest("login required"):
+        pomerium.succeed(
+            "curl -I --resolve login.required:80:127.0.0.1 http://login.required | grep -q pom-auth"
+        )
+        pomerium.succeed(
+            "curl -L --resolve login.required:80:127.0.0.1 http://login.required | grep -q 'hello I am login page'"
+        )
+  '';
+})
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
index b71ff0a1669f..4a94f0727946 100644
--- a/nixos/tests/privacyidea.nix
+++ b/nixos/tests/privacyidea.nix
@@ -12,10 +12,16 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
 
     services.privacyidea = {
       enable = true;
-      secretKey = "testing";
-      pepper = "testing";
+      secretKey = "$SECRET_KEY";
+      pepper = "$PEPPER";
       adminPasswordFile = pkgs.writeText "admin-password" "testing";
       adminEmail = "root@localhost";
+
+      # Don't try this at home!
+      environmentFile = pkgs.writeText "pi-secrets.env" ''
+        SECRET_KEY=testing
+        PEPPER=testing
+      '';
     };
     services.nginx = {
       enable = true;
@@ -29,6 +35,8 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
     machine.start()
     machine.wait_for_unit("multi-user.target")
     machine.succeed("curl --fail http://localhost | grep privacyIDEA")
+    machine.succeed("grep \"SECRET_KEY = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
+    machine.succeed("grep \"PI_PEPPER = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
     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 290cb87abbe1..62c0080dd516 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -136,6 +136,24 @@ let
       '';
     };
 
+    bitcoin = {
+      exporterConfig = {
+        enable = true;
+        rpcUser = "bitcoinrpc";
+        rpcPasswordFile = pkgs.writeText "password" "hunter2";
+      };
+      metricProvider = {
+        services.bitcoind.default.enable = true;
+        services.bitcoind.default.rpc.users.bitcoinrpc.passwordHMAC = "e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-bitcoin-exporter.service")
+        wait_for_unit("bitcoind-default.service")
+        wait_for_open_port(9332)
+        succeed("curl -sSf http://localhost:9332/metrics | grep -q '^bitcoin_blocks '")
+      '';
+    };
+
     blackbox = {
       exporterConfig = {
         enable = true;
@@ -201,6 +219,22 @@ let
       '';
     };
 
+    # Access to WHOIS server is required to properly test this exporter, so
+    # just perform basic sanity check that the exporter is running and returns
+    # a failure.
+    domain = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-domain-exporter.service")
+        wait_for_open_port(9222)
+        succeed(
+            "curl -sSf 'http://localhost:9222/probe?target=nixos.org' | grep -q 'domain_probe_success 0'"
+        )
+      '';
+    };
+
     dovecot = {
       exporterConfig = {
         enable = true;
@@ -603,6 +637,66 @@ let
       '';
     };
 
+    openldap = {
+      exporterConfig = {
+        enable = true;
+        ldapCredentialFile = "${pkgs.writeText "exporter.yml" ''
+          ldapUser: "cn=root,dc=example"
+          ldapPass: "notapassword"
+        ''}";
+      };
+      metricProvider = {
+        services.openldap = {
+          enable = true;
+          settings.children = {
+            "cn=schema".includes = [
+              "${pkgs.openldap}/etc/schema/core.ldif"
+              "${pkgs.openldap}/etc/schema/cosine.ldif"
+              "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+              "${pkgs.openldap}/etc/schema/nis.ldif"
+            ];
+            "olcDatabase={1}mdb" = {
+              attrs = {
+                objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+                olcDatabase = "{1}mdb";
+                olcDbDirectory = "/var/db/openldap";
+                olcSuffix = "dc=example";
+                olcRootDN = {
+                  # cn=root,dc=example
+                  base64 = "Y249cm9vdCxkYz1leGFtcGxl";
+                };
+                olcRootPW = {
+                  path = "${pkgs.writeText "rootpw" "notapassword"}";
+                };
+              };
+            };
+            "olcDatabase={2}monitor".attrs = {
+              objectClass = [ "olcDatabaseConfig" ];
+              olcDatabase = "{2}monitor";
+              olcAccess = [ "to dn.subtree=cn=monitor by users read" ];
+            };
+          };
+          declarativeContents."dc=example" = ''
+            dn: dc=example
+            objectClass: domain
+            dc: example
+
+            dn: ou=users,dc=example
+            objectClass: organizationalUnit
+            ou: users
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-openldap-exporter.service")
+        wait_for_open_port(389)
+        wait_for_open_port(9330)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9330/metrics | grep -q 'openldap_scrape{result=\"ok\"} 1'"
+        )
+      '';
+    };
+
     openvpn = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 0cc8bd39afbb..16979eab8217 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -45,6 +45,10 @@ import ./make-test-python.nix (
                     '';
                     inherit passwordFile initialize paths pruneOpts;
                   };
+                  remoteprune = {
+                    inherit repository passwordFile;
+                    pruneOpts = [ "--keep-last 1" ];
+                  };
                 };
 
                 environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
@@ -84,6 +88,8 @@ import ./make-test-python.nix (
               "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"',
+              "systemctl start restic-backups-remoteprune.service",
+              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
           )
         '';
       }
diff --git a/nixos/tests/snapcast.nix b/nixos/tests/snapcast.nix
index 2fef63625140..ef35d586c9c8 100644
--- a/nixos/tests/snapcast.nix
+++ b/nixos/tests/snapcast.nix
@@ -34,6 +34,10 @@ in {
             type = "tcp";
             location = "127.0.0.1:${toString tcpStreamPort}";
           };
+          meta = {
+            type = "meta";
+            location = "/mpd/bluetooth/tcp";
+          };
         };
       };
     };
diff --git a/nixos/tests/systemd-template-override.nix b/nixos/tests/systemd-template-override.nix
deleted file mode 100644
index d8ef4a6c1c9b..000000000000
--- a/nixos/tests/systemd-template-override.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-import ./make-test-python.nix {
-  name = "systemd-template-override";
-
-  machine = { pkgs, lib, ... }: let
-    touchTmp = pkgs.writeTextFile {
-      name = "touch-tmp@.service";
-      text = ''
-        [Service]
-        Type=oneshot
-        ExecStart=${pkgs.coreutils}/bin/touch /tmp/%I
-      '';
-      destination = "/etc/systemd/system/touch-tmp@.service";
-    };
-  in {
-    systemd.packages = [ touchTmp ];
-
-    systemd.services."touch-tmp@forbidden" = {
-      serviceConfig.ExecStart = [ "" ''
-        ${pkgs.coreutils}/bin/true
-      ''];
-    };
-
-    systemd.services."touch-tmp@intercept" = {
-      serviceConfig.ExecStart = [ "" ''
-        ${pkgs.coreutils}/bin/touch /tmp/renamed
-      ''];
-    };
-  };
-
-  testScript = ''
-    machine.wait_for_unit("default.target")
-
-    machine.succeed("systemctl start touch-tmp@normal")
-    machine.succeed("systemctl start touch-tmp@forbbidden")
-    machine.succeed("systemctl start touch-tmp@intercept")
-
-    machine.succeed("[ -e /tmp/normal ]")
-    machine.succeed("[ ! -e /tmp/forbidden ]")
-    machine.succeed("[ -e /tmp/renamed ]")
-  '';
-}
diff --git a/nixos/tests/systemd-unit-path.nix b/nixos/tests/systemd-unit-path.nix
new file mode 100644
index 000000000000..5998a187188a
--- /dev/null
+++ b/nixos/tests/systemd-unit-path.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  exampleScript = pkgs.writeTextFile {
+    name = "example.sh";
+    text = ''
+      #! ${pkgs.runtimeShell} -e
+
+      while true; do
+          echo "Example script running" >&2
+          ${pkgs.coreutils}/bin/sleep 1
+      done
+    '';
+    executable = true;
+  };
+
+  unitFile = pkgs.writeTextFile {
+    name = "example.service";
+    text = ''
+      [Unit]
+      Description=Example systemd service unit file
+
+      [Service]
+      ExecStart=${exampleScript}
+
+      [Install]
+      WantedBy=multi-user.target
+    '';
+  };
+in
+{
+  name = "systemd-unit-path";
+
+  machine = { pkgs, lib, ... }: {
+    boot.extraSystemdUnitPaths = [ "/etc/systemd-rw/system" ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("mkdir -p /etc/systemd-rw/system")
+    machine.succeed(
+        "cp ${unitFile} /etc/systemd-rw/system/example.service"
+    )
+    machine.succeed("systemctl start example.service")
+    machine.succeed("systemctl status example.service | grep 'Active: active'")
+  '';
+})
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
new file mode 100644
index 000000000000..9aa87d15366b
--- /dev/null
+++ b/nixos/tests/wiki-js.nix
@@ -0,0 +1,152 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "wiki-js";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = { pkgs, ... }: {
+    virtualisation.memorySize = 2048;
+    services.wiki-js = {
+      enable = true;
+      settings.db.host = "/run/postgresql";
+      settings.db.user = "wiki-js";
+      settings.logLevel = "debug";
+    };
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "wiki" ];
+      ensureUsers = [
+        { name = "wiki-js";
+          ensurePermissions."DATABASE wiki" = "ALL PRIVILEGES";
+        }
+      ];
+    };
+    systemd.services.wiki-js = {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+    };
+    environment.systemPackages = with pkgs; [ jq ];
+  };
+
+  testScript = let
+    payloads.finalize = pkgs.writeText "finalize.json" (builtins.toJSON {
+      adminEmail = "webmaster@example.com";
+      adminPassword = "notapassword";
+      adminPasswordConfirm = "notapassword";
+      siteUrl = "http://localhost:3000";
+      telemetry = false;
+    });
+    payloads.login = pkgs.writeText "login.json" (builtins.toJSON [{
+      operationName = null;
+      extensions = {};
+      query = ''
+        mutation ($username: String!, $password: String!, $strategy: String!) {
+          authentication {
+            login(username: $username, password: $password, strategy: $strategy) {
+              responseResult {
+                succeeded
+                errorCode
+                slug
+                message
+                __typename
+              }
+              jwt
+              mustChangePwd
+              mustProvideTFA
+              mustSetupTFA
+              continuationToken
+              redirect
+              tfaQRImage
+              __typename
+            }
+            __typename
+          }
+        }
+      '';
+      variables = {
+        password = "notapassword";
+        strategy = "local";
+        username = "webmaster@example.com";
+      };
+    }]);
+    payloads.content = pkgs.writeText "content.json" (builtins.toJSON [{
+      extensions = {};
+      operationName = null;
+      query = ''
+        mutation ($content: String!, $description: String!, $editor: String!, $isPrivate: Boolean!, $isPublished: Boolean!, $locale: String!, $path: String!, $publishEndDate: Date, $publishStartDate: Date, $scriptCss: String, $scriptJs: String, $tags: [String]!, $title: String!) {
+          pages {
+            create(content: $content, description: $description, editor: $editor, isPrivate: $isPrivate, isPublished: $isPublished, locale: $locale, path: $path, publishEndDate: $publishEndDate, publishStartDate: $publishStartDate, scriptCss: $scriptCss, scriptJs: $scriptJs, tags: $tags, title: $title) {
+              responseResult {
+                succeeded
+                errorCode
+                slug
+                message
+                __typename
+              }
+              page {
+                id
+                updatedAt
+                __typename
+              }
+              __typename
+            }
+            __typename
+          }
+        }
+      '';
+      variables = {
+        content = "# Header\n\nHello world!";
+        description = "";
+        editor = "markdown";
+        isPrivate = false;
+        isPublished = true;
+        locale = "en";
+        path = "home";
+        publishEndDate = "";
+        publishStartDate = "";
+        scriptCss = "";
+        scriptJs = "";
+        tags = [];
+        title = "Hello world";
+      };
+    }]);
+  in ''
+    machine.start()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl -sSf localhost:3000")
+
+    with subtest("Setup"):
+        result = machine.succeed(
+            "set -o pipefail; curl -sSf localhost:3000/finalize -X POST -d "
+            + "@${payloads.finalize} -H 'Content-Type: application/json' "
+            + "| jq .ok | xargs echo"
+        )
+        assert result.strip() == "true", f"Expected true, got {result}"
+
+        # During the setup the service gets restarted, so we use this
+        # to check if the setup is done.
+        machine.wait_until_fails("curl -sSf localhost:3000")
+        machine.wait_until_succeeds("curl -sSf localhost:3000")
+
+    with subtest("Base functionality"):
+        auth = machine.succeed(
+            "set -o pipefail; curl -sSf localhost:3000/graphql -X POST "
+            + "-d @${payloads.login} -H 'Content-Type: application/json' "
+            + "| jq '.[0].data.authentication.login.jwt' | xargs echo"
+        ).strip()
+
+        assert auth
+
+        create = machine.succeed(
+            "set -o pipefail; curl -sSf localhost:3000/graphql -X POST "
+            + "-d @${payloads.content} -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: Bearer {auth}' "
+            + "| jq '.[0].data.pages.create.responseResult.succeeded'|xargs echo"
+        )
+        assert create.strip() == "true", f"Expected true, got {create}"
+
+    machine.shutdown()
+  '';
+})
diff --git a/nixos/tests/wireguard/basic.nix b/nixos/tests/wireguard/basic.nix
index a31e92e8649d..36ab226cde0e 100644
--- a/nixos/tests/wireguard/basic.nix
+++ b/nixos/tests/wireguard/basic.nix
@@ -52,9 +52,9 @@ import ../make-test-python.nix ({ pkgs, lib, ...} :
               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
+            postSetup = let inherit (pkgs) iproute2; in ''
+              ${iproute2}/bin/ip route replace 10.23.42.1/32 dev wg0
+              ${iproute2}/bin/ip route replace fc00::1/128 dev wg0
             '';
           };
         };