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/all-tests.nix4
-rw-r--r--nixos/tests/chromium.nix14
-rw-r--r--nixos/tests/common/lxd/config.yaml24
-rw-r--r--nixos/tests/dendrite.nix1
-rw-r--r--nixos/tests/hydra/default.nix2
-rw-r--r--nixos/tests/installed-tests/default.nix1
-rw-r--r--nixos/tests/installed-tests/power-profiles-daemon.nix9
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix2
-rw-r--r--nixos/tests/installer.nix79
-rw-r--r--nixos/tests/kanidm.nix75
-rw-r--r--nixos/tests/kernel-generic.nix1
-rw-r--r--nixos/tests/lxd-image-server.nix63
-rw-r--r--nixos/tests/lxd-image.nix89
-rw-r--r--nixos/tests/lxd.nix134
-rw-r--r--nixos/tests/matrix-appservice-irc.nix3
-rw-r--r--nixos/tests/networking.nix20
-rw-r--r--nixos/tests/openssh.nix18
-rw-r--r--nixos/tests/pam/pam-oath-login.nix2
-rw-r--r--nixos/tests/pgadmin4.nix85
-rw-r--r--nixos/tests/pleroma.nix4
-rw-r--r--nixos/tests/public-inbox.nix227
-rw-r--r--nixos/tests/systemd-nspawn.nix13
-rw-r--r--nixos/tests/uptermd.nix62
-rw-r--r--nixos/tests/virtualbox.nix2
24 files changed, 626 insertions, 308 deletions
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 923464a0c9a3e..e86dda9cb3d28 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -253,6 +253,7 @@ in
   k3s-single-node = handleTest ./k3s-single-node.nix {};
   k3s-single-node-docker = handleTest ./k3s-single-node-docker.nix {};
   kafka = handleTest ./kafka.nix {};
+  kanidm = handleTest ./kanidm.nix {};
   kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
   kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
   kea = handleTest ./kea.nix {};
@@ -284,7 +285,6 @@ in
   loki = handleTest ./loki.nix {};
   lvm2 = handleTest ./lvm2 {};
   lxd = handleTest ./lxd.nix {};
-  lxd-image = handleTest ./lxd-image.nix {};
   lxd-nftables = handleTest ./lxd-nftables.nix {};
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
@@ -456,6 +456,7 @@ in
   proxy = handleTest ./proxy.nix {};
   prowlarr = handleTest ./prowlarr.nix {};
   pt2-clone = handleTest ./pt2-clone.nix {};
+  public-inbox = handleTest ./public-inbox.nix {};
   pulseaudio = discoverTests (import ./pulseaudio.nix);
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   quorum = handleTest ./quorum.nix {};
@@ -574,6 +575,7 @@ in
   unifi = handleTest ./unifi.nix {};
   unit-php = handleTest ./web-servers/unit-php.nix {};
   upnp = handleTest ./upnp.nix {};
+  uptermd = handleTest ./uptermd.nix {};
   usbguard = handleTest ./usbguard.nix {};
   user-activation-scripts = handleTest ./user-activation-scripts.nix {};
   uwsgi = handleTest ./uwsgi.nix {};
diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index 3815dca762203..6b296fe8a61ae 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -45,12 +45,14 @@ mapAttrs (channel: chromiumPkg: makeTest {
 
   enableOCR = true;
 
-  machine.imports = [ ./common/user-account.nix ./common/x11.nix ];
-  machine.virtualisation.memorySize = 2047;
-  machine.test-support.displayManager.auto.user = user;
-  machine.environment = {
-    systemPackages = [ chromiumPkg ];
-    variables."XAUTHORITY" = "/home/alice/.Xauthority";
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ./common/x11.nix ];
+    virtualisation.memorySize = 2047;
+    test-support.displayManager.auto.user = user;
+    environment = {
+      systemPackages = [ chromiumPkg ];
+      variables."XAUTHORITY" = "/home/alice/.Xauthority";
+    };
   };
 
   testScript = let
diff --git a/nixos/tests/common/lxd/config.yaml b/nixos/tests/common/lxd/config.yaml
new file mode 100644
index 0000000000000..3bb667ed43f7c
--- /dev/null
+++ b/nixos/tests/common/lxd/config.yaml
@@ -0,0 +1,24 @@
+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
diff --git a/nixos/tests/dendrite.nix b/nixos/tests/dendrite.nix
index a444c9b200189..d4a5bb1322638 100644
--- a/nixos/tests/dendrite.nix
+++ b/nixos/tests/dendrite.nix
@@ -17,6 +17,7 @@ import ./make-test-python.nix (
           homeserver = { pkgs, ... }: {
             services.dendrite = {
               enable = true;
+              openRegistration = true;
               settings = {
                 global.server_name = "test-dendrite-server.com";
                 global.private_key = private_key;
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index 9fc787842d854..baf18afbc5690 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -11,7 +11,7 @@ let
   inherit (import ./common.nix { inherit system; }) baseConfig;
 
   hydraPkgs = {
-    inherit (pkgs) hydra-unstable;
+    inherit (pkgs) hydra_unstable;
   };
 
   makeHydraTest = with pkgs.lib; name: package: makeTest {
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index fd16b481168f4..c6fb37cfe5842 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -106,6 +106,5 @@ in
   malcontent = callInstalledTest ./malcontent.nix {};
   ostree = callInstalledTest ./ostree.nix {};
   pipewire = callInstalledTest ./pipewire.nix {};
-  power-profiles-daemon = callInstalledTest ./power-profiles-daemon.nix {};
   xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {};
 }
diff --git a/nixos/tests/installed-tests/power-profiles-daemon.nix b/nixos/tests/installed-tests/power-profiles-daemon.nix
deleted file mode 100644
index 43629a0155d24..0000000000000
--- a/nixos/tests/installed-tests/power-profiles-daemon.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{ pkgs, lib, makeInstalledTest, ... }:
-
-makeInstalledTest {
-  tested = pkgs.power-profiles-daemon;
-
-  testConfig = {
-    services.power-profiles-daemon.enable = true;
-  };
-}
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
index a8b418626e660..d02387ee80e09 100644
--- a/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -27,7 +27,7 @@
     simpleUefiGrubSpecialisation
     simpleUefiSystemdBoot
     # swraid
-    # zfsroot
+    zfsroot
     ;
 
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index d8187d8e019d6..8bef4fad3dd2d 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -701,6 +701,85 @@ in {
     '';
   };
 
+  bcachefsSimple = makeInstallerTest "bcachefs-simple" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+      environment.systemPackages = with pkgs; [ keyutils ];
+    };
+
+    # We don't want to use the normal way of unlocking bcachefs defined in tasks/filesystems/bcachefs.nix.
+    # So, override initrd.postDeviceCommands completely and simply unlock with the predefined password.
+    extraConfig = ''
+      boot.initrd.postDeviceCommands = lib.mkForce "echo password | bcachefs unlock /dev/vda3";
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "keyctl link @u @s",
+        "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
+        "echo password | bcachefs unlock /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsMulti = makeInstallerTest "bcachefs-multi" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M 4096M"            # /
+        + " mkpart primary 4096M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
+        "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
   # Test a basic install using GRUB 1.
   grub1 = makeInstallerTest "grub1" rec {
     createPartitions = ''
diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix
new file mode 100644
index 0000000000000..d34f680f5224b
--- /dev/null
+++ b/nixos/tests/kanidm.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    serverDomain = certs.domain;
+  in
+  {
+    name = "kanidm";
+    meta.maintainers = with pkgs.lib.maintainers; [ erictapen Flakebi ];
+
+    nodes.server = { config, pkgs, lib, ... }: {
+      services.kanidm = {
+        enableServer = true;
+        serverSettings = {
+          origin = "https://${serverDomain}";
+          domain = serverDomain;
+          bindaddress = "[::1]:8443";
+          ldapbindaddress = "[::1]:636";
+        };
+      };
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts."${serverDomain}" = {
+          forceSSL = true;
+          sslCertificate = certs."${serverDomain}".cert;
+          sslCertificateKey = certs."${serverDomain}".key;
+          locations."/".proxyPass = "http://[::1]:8443";
+        };
+      };
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+
+      networking.hosts."::1" = [ serverDomain ];
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      users.users.kanidm.shell = pkgs.bashInteractive;
+
+      environment.systemPackages = with pkgs; [ kanidm openldap ripgrep ];
+    };
+
+    nodes.client = { pkgs, nodes, ... }: {
+      services.kanidm = {
+        enableClient = true;
+        clientSettings = {
+          uri = "https://${serverDomain}";
+        };
+      };
+
+      networking.hosts."${nodes.server.config.networking.primaryIPAddress}" = [ serverDomain ];
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+    };
+
+    testScript = { nodes, ... }:
+      let
+        ldapBaseDN = builtins.concatStringsSep "," (map (s: "dc=" + s) (pkgs.lib.splitString "." serverDomain));
+
+        # We need access to the config file in the test script.
+        filteredConfig = pkgs.lib.converge
+          (pkgs.lib.filterAttrsRecursive (_: v: v != null))
+          nodes.server.config.services.kanidm.serverSettings;
+        serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig;
+
+      in
+      ''
+        start_all()
+        server.wait_for_unit("kanidm.service")
+        server.wait_until_succeeds("curl -sf https://${serverDomain} | grep Kanidm")
+        server.wait_until_succeeds("ldapsearch -H ldap://[::1]:636 -b '${ldapBaseDN}' -x '(name=test)'")
+        client.wait_until_succeeds("kanidm login -D anonymous && kanidm self whoami | grep anonymous@${serverDomain}")
+        (rv, result) = server.execute("kanidmd recover_account -d quiet -c ${serverConfigFile} -n admin 2>&1 | rg -o '[A-Za-z0-9]{48}'")
+        assert rv == 0
+      '';
+  })
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index f34d5d6079404..1e60198abdd04 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -30,6 +30,7 @@ let
       linux_5_4_hardened
       linux_5_10_hardened
       linux_5_15_hardened
+      linux_5_17_hardened
 
       linux_testing;
   };
diff --git a/nixos/tests/lxd-image-server.nix b/nixos/tests/lxd-image-server.nix
index fa40e33e74dd6..072f4570c2c9f 100644
--- a/nixos/tests/lxd-image-server.nix
+++ b/nixos/tests/lxd-image-server.nix
@@ -1,54 +1,21 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ... } :
 
 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://tarballs.nixos.org/alpine/3.12/lxd.tar.xz";
-    hash = "sha256-1tcKaO9lOkvqfmG/7FMbfAEToAuFy2YMewS8ysBKuLA=";
-  };
-
-  alpine-rootfs = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/rootfs.tar.xz";
-    hash = "sha256-Tba9sSoaiMtQLY45u7p5DMqXTSDgs/763L/SQp0bkCA=";
+  lxd-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
   };
 
-  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
-  '';
-
+  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.system};
+  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.system};
 
 in {
   name = "lxd-image-server";
 
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ mkg20001 ];
+    maintainers = [ mkg20001 patryk27 ];
   };
 
   nodes.machine = { lib, ... }: {
@@ -100,20 +67,20 @@ in {
     # lxd expects the pool's directory to already exist
     machine.succeed("mkdir /var/lxd-pool")
 
-
     machine.succeed(
-        "cat ${lxd-config} | lxd init --preseed"
+        "cat ${./common/lxd/config.yaml} | lxd init --preseed"
     )
 
     machine.succeed(
-        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
     )
 
-    loc = "/var/www/simplestreams/images/iats/alpine/amd64/default/v1"
+    loc = "/var/www/simplestreams/images/iats/nixos/amd64/default/v1"
 
     with subtest("push image to server"):
-        machine.succeed("lxc launch alpine test")
-        machine.succeed("lxc stop test")
+        machine.succeed("lxc launch nixos test")
+        machine.sleep(5)
+        machine.succeed("lxc stop -f test")
         machine.succeed("lxc publish --public test --alias=testimg")
         machine.succeed("lxc image export testimg")
         machine.succeed("ls >&2")
diff --git a/nixos/tests/lxd-image.nix b/nixos/tests/lxd-image.nix
deleted file mode 100644
index 4930b55f19094..0000000000000
--- a/nixos/tests/lxd-image.nix
+++ /dev/null
@@ -1,89 +0,0 @@
-# This test ensures that the nixOS lxd images builds and functions properly
-# It has been extracted from `lxd.nix` to seperate failures of just the image and the lxd software
-
-import ./make-test-python.nix ({ pkgs, ...} : let
-  release = import ../release.nix {
-    /* configuration = {
-      environment.systemPackages = with pkgs; [ stdenv ]; # inject stdenv so rebuild test works
-    }; */
-  };
-
-  metadata = release.lxdMeta.${pkgs.system};
-  image = release.lxdImage.${pkgs.system};
-
-  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-image";
-
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mkg20001 ];
-  };
-
-  nodes.machine = { lib, ... }: {
-    virtualisation = {
-      # disk full otherwise
-      diskSize = 2048;
-
-      lxc.lxcfs.enable = true;
-      lxd.enable = true;
-    };
-  };
-
-  testScript = ''
-    machine.wait_for_unit("sockets.target")
-    machine.wait_for_unit("lxd.service")
-    machine.wait_for_file("/var/lib/lxd/unix.socket")
-
-    # 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"
-    )
-
-    # TODO: test custom built container aswell
-
-    with subtest("importing container works"):
-        machine.succeed("lxc image import ${metadata}/*/*.tar.xz ${image}/*/*.tar.xz --alias nixos")
-
-    with subtest("launching container works"):
-        machine.succeed("lxc launch nixos machine -c security.nesting=true")
-        # make sure machine boots up properly
-        machine.sleep(5)
-
-    with subtest("container shell works"):
-        machine.succeed("echo true | lxc exec machine /run/current-system/sw/bin/bash -")
-        machine.succeed("lxc exec machine /run/current-system/sw/bin/true")
-
-    # with subtest("rebuilding works"):
-    #     machine.succeed("lxc exec machine /run/current-system/sw/bin/nixos-rebuild switch")
-  '';
-})
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
index 162bbcc47e871..15d16564d641c 100644
--- a/nixos/tests/lxd.nix
+++ b/nixos/tests/lxd.nix
@@ -1,79 +1,18 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ... } :
 
 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-x86 = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/lxd.tar.xz";
-    hash = "sha256-1tcKaO9lOkvqfmG/7FMbfAEToAuFy2YMewS8ysBKuLA=";
-  };
-  alpine-meta-for = arch: pkgs.stdenv.mkDerivation {
-    name = "alpine-meta-${arch}";
-    version = "3.12";
-    unpackPhase = "true";
-    buildPhase = ''
-      runHook preBuild
-
-      tar xvf ${alpine-meta-x86}
-      sed -i 's/architecture: .*/architecture: ${arch}/' metadata.yaml
-
-      runHook postBuild
-    '';
-    installPhase = ''
-      runHook preInstall
-
-      tar czRf $out *
-
-      runHook postInstall
-    '';
-  };
+  lxd-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
 
-  alpine-meta = {
-    x86_64-linux = alpine-meta-x86;
-    aarch64-linux = alpine-meta-for "aarch64";
-  }.${pkgs.system} or (throw "Unsupported system: ${pkgs.system}");
-
-  alpine-rootfs = {
-    x86_64-linux = pkgs.fetchurl {
-      url = "https://tarballs.nixos.org/alpine/3.12/rootfs.tar.xz";
-      hash = "sha256-Tba9sSoaiMtQLY45u7p5DMqXTSDgs/763L/SQp0bkCA=";
-    };
-    aarch64-linux = pkgs.fetchurl {
-      url = "https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/aarch64/alpine-minirootfs-3.15.4-aarch64.tar.gz";
-      hash = "sha256-9kBz8Jwmo8XepJhTMt5zilCaHHpflnUH7y9+0To39Us=";
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [ busybox ];
     };
-  }.${pkgs.system} or (throw "Unsupported system: ${pkgs.system}");
-
-  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
-  '';
+  };
 
+  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.system};
+  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.system};
 
 in {
   name = "lxd";
@@ -84,6 +23,8 @@ in {
 
   nodes.machine = { lib, ... }: {
     virtualisation = {
+      diskSize = 2048;
+
       # Since we're testing `limits.cpu`, we've gotta have a known number of
       # cores to lean on
       cores = 2;
@@ -108,61 +49,66 @@ in {
     machine.succeed("mkdir /var/lxd-pool")
 
     machine.succeed(
-        "cat ${lxd-config} | lxd init --preseed"
+        "cat ${./common/lxd/config.yaml} | lxd init --preseed"
     )
 
     machine.succeed(
-        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
     )
 
-    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("Container can be managed"):
+        machine.succeed("lxc launch nixos container")
+        machine.sleep(5)
+        machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc exec container true")
+        machine.succeed("lxc delete -f container")
 
-    with subtest("Containers are being mounted with lxcfs inside"):
-        machine.succeed("lxc launch alpine test")
+    with subtest("Container is mounted with lxcfs inside"):
+        machine.succeed("lxc launch nixos container")
+        machine.sleep(5)
 
         ## ---------- ##
         ## limits.cpu ##
 
-        machine.succeed("lxc config set test limits.cpu 1")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.cpu 1")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
-        # 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 exec container grep -- -c ^processor /proc/cpuinfo").strip()
         )
 
-        machine.succeed("lxc config set test limits.cpu 2")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.cpu 2")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "2"
-            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+            == machine.succeed("lxc exec container grep -- -c ^processor /proc/cpuinfo").strip()
         )
 
         ## ------------- ##
         ## limits.memory ##
 
-        machine.succeed("lxc config set test limits.memory 64MB")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.memory 64MB")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "MemTotal:          62500 kB"
-            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
         )
 
-        machine.succeed("lxc config set test limits.memory 128MB")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.memory 128MB")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "MemTotal:         125000 kB"
-            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
         )
 
-        machine.succeed("lxc delete -f test")
+        machine.succeed("lxc delete -f container")
   '';
 })
diff --git a/nixos/tests/matrix-appservice-irc.nix b/nixos/tests/matrix-appservice-irc.nix
index d1c561f95dbf2..70d4585239865 100644
--- a/nixos/tests/matrix-appservice-irc.nix
+++ b/nixos/tests/matrix-appservice-irc.nix
@@ -20,6 +20,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
               enable_registration = true;
 
+              # don't use this in production, always use some form of verification
+              enable_registration_without_verification = true;
+
               listeners = [ {
                 # The default but tls=false
                 bind_addresses = [
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index a1150097a0917..2cc1e9b0942ca 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -139,6 +139,26 @@ let
               client.wait_until_succeeds("ping -c 1 192.168.3.1")
         '';
     };
+    dhcpDefault = {
+      name = "useDHCP-by-default";
+      nodes.router = router;
+      nodes.client = { lib, ... }: {
+        # Disable test driver default config
+        networking.interfaces = lib.mkForce {};
+        networking.useNetworkd = networkd;
+        virtualisation.vlans = [ 1 ];
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("multi-user.target")
+        client.wait_until_succeeds("ip addr show dev eth1 | grep '192.168.1'")
+        client.shell_interact()
+        client.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.2")
+        client.succeed("ping -c 1 192.168.1.2")
+      '';
+    };
     dhcpSimple = {
       name = "SimpleDHCP";
       nodes.router = router;
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index 003813379e697..4083f5906d79a 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -80,17 +80,21 @@ in {
 
         client.wait_for_unit("network.target")
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024",
+            timeout=30
         )
 
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024",
+            timeout=30
         )
 
     with subtest("configured-authkey"):
@@ -99,10 +103,12 @@ in {
         )
         client.succeed("chmod 600 privkey.snakeoil")
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server_lazy true"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server_lazy true",
+            timeout=30
         )
 
     with subtest("localhost-only"):
diff --git a/nixos/tests/pam/pam-oath-login.nix b/nixos/tests/pam/pam-oath-login.nix
index 8fb7553de9073..c532e81e674d7 100644
--- a/nixos/tests/pam/pam-oath-login.nix
+++ b/nixos/tests/pam/pam-oath-login.nix
@@ -7,7 +7,7 @@ let
   # how many passwords have been made. In this env, we'll always be on
   # the 0th counter, so the password is static.
   #
-  # Generated in nix-shell -p oathToolkit
+  # Generated in nix-shell -p oath-toolkit
   # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
   # and picking a the first 4:
   oathSnakeOilPassword1 = "143349";
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
index 2f6dc3bd569ff..b30299d307eb9 100644
--- a/nixos/tests/pgadmin4.nix
+++ b/nixos/tests/pgadmin4.nix
@@ -1,53 +1,27 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+import ./make-test-python.nix ({ pkgs, lib, buildDeps ? [ ], pythonEnv ? [ ], ... }:
+
+  /*
+  This test suite replaces the typical pytestCheckHook function in python
+  packages. Pgadmin4 test suite needs a running and configured postgresql
+  server. This is why this test exists.
+
+  To not repeat all the python dependencies needed, this test is called directly
+  from the pgadmin4 derivation, which also passes the currently
+  used propagatedBuildInputs and any python overrides.
+
+  Unfortunately, there doesn't seem to be an easy way to otherwise include
+  the needed packages here.
+
+  Due the the needed parameters a direct call to "nixosTests.pgadmin4" fails
+  and needs to be called as "pgadmin4.tests"
+
+  */
 
   let
     pgadmin4SrcDir = "/pgadmin";
     pgadmin4Dir = "/var/lib/pgadmin";
     pgadmin4LogDir = "/var/log/pgadmin";
 
-    python-with-needed-packages = pkgs.python3.withPackages (ps: with ps; [
-      selenium
-      testtools
-      testscenarios
-      flask
-      flask-babelex
-      flask-babel
-      flask-gravatar
-      flask_login
-      flask_mail
-      flask_migrate
-      flask_sqlalchemy
-      flask_wtf
-      flask-compress
-      passlib
-      pytz
-      simplejson
-      six
-      sqlparse
-      wtforms
-      flask-paranoid
-      psutil
-      psycopg2
-      python-dateutil
-      sqlalchemy
-      itsdangerous
-      flask-security-too
-      bcrypt
-      cryptography
-      sshtunnel
-      ldap3
-      gssapi
-      flask-socketio
-      eventlet
-      httpagentparser
-      user-agents
-      wheel
-      authlib
-      qrcode
-      pillow
-      pyotp
-      boto3
-    ]);
   in
   {
     name = "pgadmin4";
@@ -55,12 +29,27 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
     nodes.machine = { pkgs, ... }: {
       imports = [ ./common/x11.nix ];
+      # needed because pgadmin 6.8 will fail, if those dependencies get updated
+      nixpkgs.overlays = [
+        (self: super: {
+          pythonPackages = pythonEnv;
+        })
+      ];
+
       environment.systemPackages = with pkgs; [
         pgadmin4
         postgresql
-        python-with-needed-packages
         chromedriver
         chromium
+        # include the same packages as in pgadmin minus speaklater3
+        (python3.withPackages
+          (ps: buildDeps ++
+            [
+              # test suite package requirements
+              pythonPackages.testscenarios
+              pythonPackages.selenium
+            ])
+        )
       ];
       services.postgresql = {
         enable = true;
@@ -121,7 +110,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       with subtest("run browser test"):
           machine.succeed(
                'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg browser --exclude \
+               && python regression/runtests.py --pkg browser --exclude \
                browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login'
           )
 
@@ -131,13 +120,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           machine.succeed(
               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
                && export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [];}} \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg feature_tests'
+               && python regression/runtests.py --pkg feature_tests'
           )
 
       with subtest("run resql test"):
           machine.succeed(
                'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg resql'
+               && python regression/runtests.py --pkg resql'
           )
     '';
   })
diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index 90a9a25110447..8998716243a25 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -158,7 +158,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
     # Waiting for pleroma to be up.
     timeout 5m bash -c 'while [[ "$(curl -s -o /dev/null -w '%{http_code}' https://pleroma.nixos.test/api/v1/instance)" != "200" ]]; do sleep 2; done'
-    pleroma_ctl user new jamy jamy@nixos.test --password 'jamy-password' --moderator --admin -y
+    # Toremove the RELEASE_COOKIE bit when https://github.com/NixOS/nixpkgs/issues/166229 gets fixed.
+    RELEASE_COOKIE="/var/lib/pleroma/.cookie" \
+      pleroma_ctl user new jamy jamy@nixos.test --password 'jamy-password' --moderator --admin -y
   '';
 
   tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
diff --git a/nixos/tests/public-inbox.nix b/nixos/tests/public-inbox.nix
new file mode 100644
index 0000000000000..7de40400fcbf5
--- /dev/null
+++ b/nixos/tests/public-inbox.nix
@@ -0,0 +1,227 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  orga = "example";
+  domain = "${orga}.localdomain";
+
+  tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=machine.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+in
+{
+  name = "public-inbox";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ julm ];
+
+  machine = { config, pkgs, nodes, ... }: let
+    inherit (config.services) gitolite public-inbox;
+    # Git repositories paths in Gitolite.
+    # Only their baseNameOf is used for configuring public-inbox.
+    repositories = [
+      "user/repo1"
+      "user/repo2"
+    ];
+  in
+  {
+    virtualisation.diskSize = 1 * 1024;
+    virtualisation.memorySize = 1 * 1024;
+    networking.domain = domain;
+
+    security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    # If using security.acme:
+    #security.acme.certs."${domain}".postRun = ''
+    #  systemctl try-restart public-inbox-nntpd public-inbox-imapd
+    #'';
+
+    services.public-inbox = {
+      enable = true;
+      postfix.enable = true;
+      openFirewall = true;
+      settings.publicinbox = {
+        css = [ "href=https://machine.${domain}/style/light.css" ];
+        nntpserver = [ "nntps://machine.${domain}" ];
+        wwwlisting = "match=domain";
+      };
+      mda = {
+        enable = true;
+        args = [ "--no-precheck" ]; # Allow Bcc:
+      };
+      http = {
+        enable = true;
+        port = "/run/public-inbox-http.sock";
+        #port = 8080;
+        args = ["-W0"];
+        mounts = [
+          "https://machine.${domain}/inbox"
+        ];
+      };
+      nntp = {
+        enable = true;
+        #port = 563;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      imap = {
+        enable = true;
+        #port = 993;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      inboxes = lib.recursiveUpdate (lib.genAttrs (map baseNameOf repositories) (repo: {
+        address = [
+          # Routed to the "public-inbox:" transport in services.postfix.transport
+          "${repo}@${domain}"
+        ];
+        description = ''
+          ${repo}@${domain} :
+          discussions about ${repo}.
+        '';
+        url = "https://machine.${domain}/inbox/${repo}";
+        newsgroup = "inbox.comp.${orga}.${repo}";
+        coderepo = [ repo ];
+      }))
+      {
+        repo2 = {
+          hide = [
+            "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1
+            "manifest"
+            "www"
+          ];
+        };
+      };
+      settings.coderepo = lib.listToAttrs (map (path: lib.nameValuePair (baseNameOf path) {
+        dir = "/var/lib/gitolite/repositories/${path}.git";
+        cgitUrl = "https://git.${domain}/${path}.git";
+      }) repositories);
+    };
+
+    # Use gitolite to store Git repositories listed in coderepo entries
+    services.gitolite = {
+      enable = true;
+      adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmoTOQnGqX+//us5oye8UuE+tQBx9QEM7PN13jrwgqY root@localhost";
+    };
+    systemd.services.public-inbox-httpd = {
+      serviceConfig.SupplementaryGroups = [ gitolite.group ];
+    };
+
+    # Use nginx as a reverse proxy for public-inbox-httpd
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      recommendedOptimisation = true;
+      recommendedTlsSettings = true;
+      recommendedProxySettings = true;
+      virtualHosts."machine.${domain}" = {
+        forceSSL = true;
+        sslCertificate = "${tls-cert}/cert.pem";
+        sslCertificateKey = "${tls-cert}/key.pem";
+        locations."/".return = "302 /inbox";
+        locations."= /inbox".return = "302 /inbox/";
+        locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox";
+        # If using TCP instead of a Unix socket:
+        #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox";
+        # Referred to by settings.publicinbox.css
+        # See http://public-inbox.org/meta/_/text/color/
+        locations."= /style/light.css".alias = pkgs.writeText "light.css" ''
+          * { background:#fff; color:#000 }
+
+          a { color:#00f; text-decoration:none }
+          a:visited { color:#808 }
+
+          *.q { color:#008 }
+
+          *.add { color:#060 }
+          *.del {color:#900 }
+          *.head { color:#000 }
+          *.hunk { color:#960 }
+
+          .hl.num { color:#f30 } /* number */
+          .hl.esc { color:#f0f } /* escape character */
+          .hl.str { color:#f30 } /* string */
+          .hl.ppc { color:#c3c } /* preprocessor */
+          .hl.pps { color:#f30 } /* preprocessor string */
+          .hl.slc { color:#099 } /* single-line comment */
+          .hl.com { color:#099 } /* multi-line comment */
+          /* .hl.opt { color:#ccc } */ /* operator */
+          /* .hl.ipl { color:#ccc } */ /* interpolation */
+
+          /* keyword groups kw[a-z] */
+          .hl.kwa { color:#f90 }
+          .hl.kwb { color:#060 }
+          .hl.kwc { color:#f90 }
+          /* .hl.kwd { color:#ccc } */
+        '';
+      };
+    };
+
+    services.postfix = {
+      enable = true;
+      setSendmail = true;
+      #sslCert = "${tls-cert}/cert.pem";
+      #sslKey = "${tls-cert}/key.pem";
+      recipientDelimiter = "+";
+    };
+
+    environment.systemPackages = [
+      pkgs.mailutils
+      pkgs.openssl
+    ];
+
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("public-inbox-init.service")
+
+    # Very basic check that Gitolite can work;
+    # Gitolite is not needed for the rest of this testScript
+    machine.wait_for_unit("gitolite-init.service")
+
+    # List inboxes through public-inbox-httpd
+    machine.wait_for_unit("nginx.service")
+    machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}")
+    # The repo2 inbox is hidden
+    machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}")
+    machine.wait_for_unit("public-inbox-httpd.service")
+
+    # Send a mail and read it through public-inbox-httpd
+    # Must work too when using a recipientDelimiter.
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("mail -t <${pkgs.writeText "mail" ''
+      Subject: Testing mail
+      From: root@localhost
+      To: repo1+extension@${domain}
+      Message-ID: <repo1@root-1>
+      Content-Type: text/plain; charset=utf-8
+      Content-Disposition: inline
+
+      This is a testing mail.
+    ''}")
+    machine.sleep(5)
+    machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'")
+
+    # Read a mail through public-inbox-imapd
+    machine.wait_for_open_port(993)
+    machine.wait_for_unit("public-inbox-imapd.service")
+    machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" ''
+      tag login anonymous@${domain} anonymous
+      tag SELECT INBOX.comp.${orga}.repo1.0
+      tag FETCH 1 (BODY[HEADER])
+      tag LOGOUT
+    ''} | grep '^Message-ID: <repo1@root-1>'")
+
+    # TODO: Read a mail through public-inbox-nntpd
+    #machine.wait_for_open_port(563)
+    #machine.wait_for_unit("public-inbox-nntpd.service")
+
+    # Delete a mail.
+    # Note that the use of an extension not listed in the addresses
+    # require to use --all
+    machine.succeed("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
+    machine.fail("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
+  '';
+})
diff --git a/nixos/tests/systemd-nspawn.nix b/nixos/tests/systemd-nspawn.nix
index 5bf55060d2e03..c2cb92d11301c 100644
--- a/nixos/tests/systemd-nspawn.nix
+++ b/nixos/tests/systemd-nspawn.nix
@@ -25,8 +25,15 @@ let
   nspawnImages = (pkgs.runCommand "localhost" { buildInputs = [ pkgs.coreutils pkgs.gnupg ]; } ''
     mkdir -p $out
     cd $out
+
+    # produce a testimage.raw
     dd if=/dev/urandom of=$out/testimage.raw bs=$((1024*1024+7)) count=5
-    sha256sum testimage.raw > SHA256SUMS
+
+    # produce a testimage2.tar.xz, containing the hello store path
+    tar cvJpf testimage2.tar.xz ${pkgs.hello}
+
+    # produce signature(s)
+    sha256sum testimage* > SHA256SUMS
     export GNUPGHOME="$(mktemp -d)"
     cp -R ${gpgKeyring}/* $GNUPGHOME
     gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS
@@ -56,5 +63,9 @@ in {
     client.succeed(
         "cmp /var/lib/machines/testimage.raw ${nspawnImages}/testimage.raw"
     )
+    client.succeed("machinectl pull-tar --verify=signature http://server/testimage2.tar.xz")
+    client.succeed(
+        "cmp /var/lib/machines/testimage2/${pkgs.hello}/bin/hello ${pkgs.hello}/bin/hello"
+    )
   '';
 })
diff --git a/nixos/tests/uptermd.nix b/nixos/tests/uptermd.nix
new file mode 100644
index 0000000000000..b2ff9a1e0d9cf
--- /dev/null
+++ b/nixos/tests/uptermd.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+
+let
+  client = {pkgs, ...}:{
+    environment.systemPackages = [ pkgs.upterm ];
+  };
+in
+{
+  name = "uptermd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fleaz ];
+  };
+
+  nodes = {
+    server = {config, ...}: {
+      services.uptermd = {
+        enable = true;
+        openFirewall = true;
+        port = 1337;
+      };
+    };
+    client1 = client;
+    client2 = client;
+  };
+
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("uptermd.service")
+    server.wait_for_unit("network-online.target")
+
+    # Add SSH hostkeys from the server to both clients
+    # uptermd needs an '@cert-authority entry so we need to modify the known_hosts file
+    client1.execute("sleep 3; mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client1.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+    client2.execute("sleep 3; mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client2.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+
+    client1.wait_for_unit("multi-user.target")
+    client1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    client1.wait_until_tty_matches(1, "login: ")
+    client1.send_chars("root\n")
+    client1.wait_until_succeeds("pgrep -u root bash")
+
+    client1.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+    client1.send_chars("TERM=xterm upterm host --server ssh://server:1337 --force-command hostname -- bash > /tmp/session-details\n")
+    client1.wait_for_file("/tmp/session-details")
+    client1.send_key("q")
+
+    # uptermd can't connect if we don't have a keypair
+    client2.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+
+    # Grep the ssh connect command from the output of 'upterm host'
+    ssh_command = client1.succeed("grep 'SSH Session' /tmp/session-details | cut -d':' -f2-").strip()
+
+    # Connect with client2. Because we used '--force-command hostname' we should get "client1" as the output
+    output = client2.succeed(ssh_command)
+
+    assert output.strip() == "client1"
+  '';
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 27093aab96ee5..4eb402a7d36ef 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -222,7 +222,7 @@ let
               machine.execute(ru("VBoxManage controlvm ${name} poweroff"))
           machine.succeed("rm -rf ${sharePath}")
           machine.succeed("mkdir -p ${sharePath}")
-          machine.succeed("chown alice.users ${sharePath}")
+          machine.succeed("chown alice:users ${sharePath}")
 
 
       def create_vm_${name}():