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/adguardhome.nix85
-rw-r--r--nixos/tests/all-tests.nix11
-rw-r--r--nixos/tests/caddy.nix2
-rw-r--r--nixos/tests/castopod.nix2
-rw-r--r--nixos/tests/coder.nix4
-rw-r--r--nixos/tests/db-rest.nix107
-rw-r--r--nixos/tests/docker-registry.nix2
-rw-r--r--nixos/tests/earlyoom.nix2
-rw-r--r--nixos/tests/firefly-iii.nix26
-rw-r--r--nixos/tests/forgejo.nix162
-rw-r--r--nixos/tests/freshrss-none-auth.nix19
-rw-r--r--nixos/tests/gnome-extensions.nix2
-rw-r--r--nixos/tests/gonic.nix12
-rw-r--r--nixos/tests/gvisor.nix2
-rw-r--r--nixos/tests/incus/virtual-machine.nix20
-rw-r--r--nixos/tests/isolate.nix38
-rw-r--r--nixos/tests/libinput.nix2
-rw-r--r--nixos/tests/lomiri.nix17
-rw-r--r--nixos/tests/networking/networkmanager.nix172
-rw-r--r--nixos/tests/nginx-sso.nix2
-rw-r--r--nixos/tests/openssh.nix56
-rw-r--r--nixos/tests/paperless.nix1
-rw-r--r--nixos/tests/phosh.nix11
-rw-r--r--nixos/tests/predictable-interface-names.nix2
-rw-r--r--nixos/tests/prometheus-exporters.nix50
-rw-r--r--nixos/tests/promscale.nix60
-rw-r--r--nixos/tests/qtile.nix34
-rw-r--r--nixos/tests/radicale.nix2
-rw-r--r--nixos/tests/sunshine.nix70
-rw-r--r--nixos/tests/swayfx.nix207
-rw-r--r--nixos/tests/switch-test.nix21
-rw-r--r--nixos/tests/syncthing-relay.nix2
-rw-r--r--nixos/tests/systemd-networkd-bridge.nix103
-rw-r--r--nixos/tests/systemd.nix13
-rw-r--r--nixos/tests/teleport.nix2
-rw-r--r--nixos/tests/web-apps/gotosocial.nix2
-rw-r--r--nixos/tests/wpa_supplicant.nix29
37 files changed, 1159 insertions, 195 deletions
diff --git a/nixos/tests/adguardhome.nix b/nixos/tests/adguardhome.nix
index 80613ce825340..005d54e17dfdc 100644
--- a/nixos/tests/adguardhome.nix
+++ b/nixos/tests/adguardhome.nix
@@ -2,41 +2,39 @@
   name = "adguardhome";
 
   nodes = {
-    nullConf = { ... }: { services.adguardhome = { enable = true; }; };
+    nullConf = { services.adguardhome.enable = true; };
 
-    emptyConf = { lib, ... }: {
+    emptyConf = {
       services.adguardhome = {
         enable = true;
+
+        settings = { };
+      };
+    };
+
+    schemaVersionBefore23 = {
+      services.adguardhome = {
+        enable = true;
+
+        settings.schema_version = 20;
       };
     };
 
-    declarativeConf = { ... }: {
+    declarativeConf = {
       services.adguardhome = {
         enable = true;
 
         mutableSettings = false;
-        settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
-        };
+        settings.dns.bootstrap_dns = [ "127.0.0.1" ];
       };
     };
 
-    mixedConf = { ... }: {
+    mixedConf = {
       services.adguardhome = {
         enable = true;
 
         mutableSettings = true;
-        settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
-        };
+        settings.dns.bootstrap_dns = [ "127.0.0.1" ];
       };
     };
 
@@ -70,11 +68,7 @@
         allowDHCP = true;
         mutableSettings = false;
         settings = {
-          schema_version = 0;
-          dns = {
-            bind_host = "0.0.0.0";
-            bootstrap_dns = "127.0.0.1";
-          };
+          dns.bootstrap_dns = [ "127.0.0.1" ];
           dhcp = {
             # This implicitly enables CAP_NET_RAW
             enabled = true;
@@ -104,33 +98,38 @@
 
   testScript = ''
     with subtest("Minimal (settings = null) config test"):
-        nullConf.wait_for_unit("adguardhome.service")
+      nullConf.wait_for_unit("adguardhome.service")
+      nullConf.wait_for_open_port(3000)
 
     with subtest("Default config test"):
-        emptyConf.wait_for_unit("adguardhome.service")
-        emptyConf.wait_for_open_port(3000)
+      emptyConf.wait_for_unit("adguardhome.service")
+      emptyConf.wait_for_open_port(3000)
+
+    with subtest("Default schema_version 23 config test"):
+      schemaVersionBefore23.wait_for_unit("adguardhome.service")
+      schemaVersionBefore23.wait_for_open_port(3000)
 
     with subtest("Declarative config test, DNS will be reachable"):
-        declarativeConf.wait_for_unit("adguardhome.service")
-        declarativeConf.wait_for_open_port(53)
-        declarativeConf.wait_for_open_port(3000)
+      declarativeConf.wait_for_unit("adguardhome.service")
+      declarativeConf.wait_for_open_port(53)
+      declarativeConf.wait_for_open_port(3000)
 
     with subtest("Mixed config test, check whether merging works"):
-        mixedConf.wait_for_unit("adguardhome.service")
-        mixedConf.wait_for_open_port(53)
-        mixedConf.wait_for_open_port(3000)
-        # Test whether merging works properly, even if nothing is changed
-        mixedConf.systemctl("restart adguardhome.service")
-        mixedConf.wait_for_unit("adguardhome.service")
-        mixedConf.wait_for_open_port(3000)
+      mixedConf.wait_for_unit("adguardhome.service")
+      mixedConf.wait_for_open_port(53)
+      mixedConf.wait_for_open_port(3000)
+      # Test whether merging works properly, even if nothing is changed
+      mixedConf.systemctl("restart adguardhome.service")
+      mixedConf.wait_for_unit("adguardhome.service")
+      mixedConf.wait_for_open_port(3000)
 
     with subtest("Testing successful DHCP start"):
-        dhcpConf.wait_for_unit("adguardhome.service")
-        client.systemctl("start network-online.target")
-        client.wait_for_unit("network-online.target")
-        # Test IP assignment via DHCP
-        dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
-        # Test hostname resolution over DHCP-provided DNS
-        dhcpConf.wait_until_succeeds("ping -c 5 client.lan")
+      dhcpConf.wait_for_unit("adguardhome.service")
+      client.systemctl("start network-online.target")
+      client.wait_for_unit("network-online.target")
+      # Test IP assignment via DHCP
+      dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
+      # Test hostname resolution over DHCP-provided DNS
+      dhcpConf.wait_until_succeeds("ping -c 5 client.lan")
   '';
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index fd5a0186bdecf..6ef1d8d537980 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -236,6 +236,7 @@ in {
   darling = handleTest ./darling.nix {};
   dae = handleTest ./dae.nix {};
   davis = handleTest ./davis.nix {};
+  db-rest = handleTest ./db-rest.nix {};
   dconf = handleTest ./dconf.nix {};
   deconz = handleTest ./deconz.nix {};
   deepin = handleTest ./deepin.nix {};
@@ -307,6 +308,7 @@ in {
   ferm = handleTest ./ferm.nix {};
   ferretdb = handleTest ./ferretdb.nix {};
   filesystems-overlayfs = runTest ./filesystems-overlayfs.nix;
+  firefly-iii = handleTest ./firefly-iii.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
   firefox-beta = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-beta; };
   firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; };
@@ -329,6 +331,7 @@ in {
   freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
   freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
   freshrss-http-auth = handleTest ./freshrss-http-auth.nix {};
+  freshrss-none-auth = handleTest ./freshrss-none-auth.nix {};
   frigate = handleTest ./frigate.nix {};
   frp = handleTest ./frp.nix {};
   frr = handleTest ./frr.nix {};
@@ -396,6 +399,7 @@ in {
   honk = runTest ./honk.nix;
   installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
   invidious = handleTest ./invidious.nix {};
+  isolate = handleTest ./isolate.nix {};
   livebook-service = handleTest ./livebook-service.nix {};
   pyload = handleTest ./pyload.nix {};
   oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
@@ -597,6 +601,7 @@ in {
   netdata = handleTest ./netdata.nix {};
   networking.scripted = handleTest ./networking/networkd-and-scripted.nix { networkd = false; };
   networking.networkd = handleTest ./networking/networkd-and-scripted.nix { networkd = true; };
+  networking.networkmanager = handleTest ./networking/networkmanager.nix {};
   netbox_3_6 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_6; };
   netbox_3_7 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_7; };
   netbox-upgrade = handleTest ./web-apps/netbox-upgrade.nix {};
@@ -768,6 +773,7 @@ in {
   qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; };
   qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; };
   qownnotes = handleTest ./qownnotes.nix {};
+  qtile = handleTest ./qtile.nix {};
   quake3 = handleTest ./quake3.nix {};
   quicktun = handleTest ./quicktun.nix {};
   quorum = handleTest ./quorum.nix {};
@@ -778,6 +784,7 @@ in {
   rasdaemon = handleTest ./rasdaemon.nix {};
   readarr = handleTest ./readarr.nix {};
   redis = handleTest ./redis.nix {};
+  redlib = handleTest ./redlib.nix {};
   redmine = handleTest ./redmine.nix {};
   restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
   restic-rest-server = handleTest ./restic-rest-server.nix {};
@@ -854,11 +861,13 @@ in {
   stunnel = handleTest ./stunnel.nix {};
   sudo = handleTest ./sudo.nix {};
   sudo-rs = handleTest ./sudo-rs.nix {};
+  sunshine = handleTest ./sunshine.nix {};
   suwayomi-server = handleTest ./suwayomi-server.nix {};
   swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
   swap-partition = handleTest ./swap-partition.nix {};
   swap-random-encryption = handleTest ./swap-random-encryption.nix {};
   sway = handleTest ./sway.nix {};
+  swayfx = handleTest ./swayfx.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
   syncthing = handleTest ./syncthing.nix {};
@@ -900,6 +909,7 @@ in {
   systemd-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix;
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
+  systemd-networkd-bridge = handleTest ./systemd-networkd-bridge.nix {};
   systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
   systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
   systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
@@ -935,7 +945,6 @@ in {
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
   timescaledb = handleTest ./timescaledb.nix {};
-  promscale = handleTest ./promscale.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix
index 41d8e57de4686..0efe8f94e39dd 100644
--- a/nixos/tests/caddy.nix
+++ b/nixos/tests/caddy.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "caddy";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ xfix Br1ght0ne ];
+    maintainers = [ Br1ght0ne ];
   };
 
   nodes = {
diff --git a/nixos/tests/castopod.nix b/nixos/tests/castopod.nix
index 29bf8e8cacd89..3257cd3d363c7 100644
--- a/nixos/tests/castopod.nix
+++ b/nixos/tests/castopod.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 {
   name = "castopod";
   meta = with lib.maintainers; {
-    maintainers = [ alexoundos misuzu ];
+    maintainers = [ alexoundos ];
   };
 
   nodes.castopod = { nodes, ... }: {
diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix
index 12813827284b9..fd1fa0cc3031f 100644
--- a/nixos/tests/coder.nix
+++ b/nixos/tests/coder.nix
@@ -1,8 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "coder";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ shyim ghuntley ];
-  };
+  meta.maintainers = pkgs.coder.meta.maintainers;
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixos/tests/db-rest.nix b/nixos/tests/db-rest.nix
new file mode 100644
index 0000000000000..9249da904acbe
--- /dev/null
+++ b/nixos/tests/db-rest.nix
@@ -0,0 +1,107 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "db-rest";
+  meta.maintainers = with pkgs.lib.maintainers; [ marie ];
+
+  nodes = {
+    database = {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.10"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 31638 ];
+      };
+
+      services.redis.servers.db-rest = {
+        enable = true;
+        bind = "0.0.0.0";
+        requirePass = "choochoo";
+        port = 31638;
+      };
+    };
+
+    serverWithTcp = { pkgs, ... }: {
+      environment = {
+        etc = {
+          "db-rest/password-redis-db".text = ''
+            choochoo
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.11"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 3000 ];
+      };
+
+      services.db-rest = {
+        enable = true;
+        host = "0.0.0.0";
+        redis = {
+          enable = true;
+          createLocally = false;
+          host = "192.168.2.10";
+          port = 31638;
+          passwordFile = "/etc/db-rest/password-redis-db";
+          useSSL = false;
+        };
+      };
+    };
+
+    serverWithUnixSocket = { pkgs, ... }: {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.12"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 3000 ];
+      };
+
+      services.db-rest = {
+        enable = true;
+        host = "0.0.0.0";
+        redis = {
+          enable = true;
+          createLocally = true;
+        };
+      };
+    };
+
+    client = {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.13"; prefixLength = 24; }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("db-rest redis with TCP socket"):
+      database.wait_for_unit("redis-db-rest.service")
+      database.wait_for_open_port(31638)
+
+      serverWithTcp.wait_for_unit("db-rest.service")
+      serverWithTcp.wait_for_open_port(3000)
+
+      client.succeed("curl --fail --get http://192.168.2.11:3000/stations --data-urlencode 'query=Köln Hbf' | jq -r '.\"8000207\".name' | grep 'Köln Hbf'")
+
+    with subtest("db-rest redis with Unix socket"):
+      serverWithUnixSocket.wait_for_unit("db-rest.service")
+      serverWithUnixSocket.wait_for_open_port(3000)
+
+      client.succeed("curl --fail --get http://192.168.2.12:3000/stations --data-urlencode 'query=Köln Hbf' | jq -r '.\"8000207\".name' | grep 'Köln Hbf'")
+  '';
+})
diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix
index 3969ef3f0226f..4f033fc30b191 100644
--- a/nixos/tests/docker-registry.nix
+++ b/nixos/tests/docker-registry.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "docker-registry";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ globin ironpinguin ];
+    maintainers = [ globin ironpinguin cafkafk ];
   };
 
   nodes = {
diff --git a/nixos/tests/earlyoom.nix b/nixos/tests/earlyoom.nix
index 75bdf56899b30..b7850ddeaaab3 100644
--- a/nixos/tests/earlyoom.nix
+++ b/nixos/tests/earlyoom.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ lib, ... }: {
   name = "earlyoom";
   meta = {
-    maintainers = with lib.maintainers; [ ncfavier ];
+    maintainers = with lib.maintainers; [ ncfavier AndersonTorres ];
   };
 
   machine = {
diff --git a/nixos/tests/firefly-iii.nix b/nixos/tests/firefly-iii.nix
new file mode 100644
index 0000000000000..c93d799320a48
--- /dev/null
+++ b/nixos/tests/firefly-iii.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "firefly-iii";
+  meta.maintainers = [ lib.maintainers.savyajha ];
+
+  nodes.machine = { config, ... }: {
+    environment.etc = {
+      "firefly-iii-appkey".text = "TestTestTestTestTestTestTestTest";
+    };
+    services.firefly-iii = {
+      enable = true;
+      virtualHost = "http://localhost";
+      enableNginx = true;
+      settings = {
+        APP_KEY_FILE = "/etc/firefly-iii-appkey";
+        LOG_CHANNEL = "stdout";
+        SITE_OWNER = "mail@example.com";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("phpfpm-firefly-iii.service")
+    machine.wait_for_unit("nginx.service")
+    machine.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+  '';
+})
diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix
index b14df0a2c74f9..827fae2790c6e 100644
--- a/nixos/tests/forgejo.nix
+++ b/nixos/tests/forgejo.nix
@@ -22,8 +22,27 @@ let
   '';
   signingPrivateKeyId = "4D642DE8B678C79D";
 
+  actionsWorkflowYaml = ''
+    run-name: dummy workflow
+    on:
+      push:
+    jobs:
+      cat:
+        runs-on: native
+        steps:
+          - uses: http://localhost:3000/test/checkout@main
+          - run: cat testfile
+  '';
+  # https://github.com/actions/checkout/releases
+  checkoutActionSource = pkgs.fetchFromGitHub {
+    owner = "actions";
+    repo = "checkout";
+    rev = "v4.1.1";
+    hash = "sha256-h2/UIp8IjPo3eE4Gzx52Fb7pcgG/Ww7u31w5fdKVMos=";
+  };
+
   supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
-  makeGForgejoTest = type: nameValuePair type (makeTest {
+  makeForgejoTest = type: nameValuePair type (makeTest {
     name = "forgejo-${type}";
     meta.maintainers = with maintainers; [ bendlas emilylange ];
 
@@ -36,21 +55,28 @@ let
           settings.service.DISABLE_REGISTRATION = true;
           settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
           settings.actions.ENABLED = true;
+          settings.repository = {
+            ENABLE_PUSH_CREATE_USER = true;
+            DEFAULT_PUSH_CREATE_PRIVATE = false;
+          };
         };
-        environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file ];
+        environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file pkgs.htmlq ];
         services.openssh.enable = true;
 
         specialisation.runner = {
           inheritParentConfig = true;
-          configuration.services.gitea-actions-runner.instances."test" = {
-            enable = true;
-            name = "ci";
-            url = "http://localhost:3000";
-            labels = [
-              # don't require docker/podman
-              "native:host"
-            ];
-            tokenFile = "/var/lib/forgejo/runner_token";
+          configuration.services.gitea-actions-runner = {
+            package = pkgs.forgejo-runner;
+            instances."test" = {
+              enable = true;
+              name = "ci";
+              url = "http://localhost:3000";
+              labels = [
+                # type ":host" does not depend on docker/podman/lxc
+                "native:host"
+              ];
+              tokenFile = "/var/lib/forgejo/runner_token";
+            };
           };
         };
         specialisation.dump = {
@@ -62,11 +88,20 @@ let
           };
         };
       };
-      client1 = { config, pkgs, ... }: {
-        environment.systemPackages = [ pkgs.git ];
-      };
-      client2 = { config, pkgs, ... }: {
-        environment.systemPackages = [ pkgs.git ];
+      client = { ... }: {
+        programs.git = {
+          enable = true;
+          config = {
+            user.email = "test@localhost";
+            user.name = "test";
+            init.defaultBranch = "main";
+          };
+        };
+        programs.ssh.extraConfig = ''
+          Host *
+            StrictHostKeyChecking no
+            IdentityFile ~/.ssh/privk
+        '';
       };
     };
 
@@ -75,26 +110,23 @@ let
         inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
         serverSystem = nodes.server.system.build.toplevel;
         dumpFile = with nodes.server.specialisation.dump.configuration.services.forgejo.dump; "${backupDir}/${file}";
+        remoteUri = "forgejo@server:test/repo";
+        remoteUriCheckoutAction = "forgejo@server:test/checkout";
       in
       ''
         import json
-        GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
-        REPO = "forgejo@server:test/repo"
-        PRIVK = "${snakeOilPrivateKey}"
 
         start_all()
 
-        client1.succeed("mkdir /tmp/repo")
-        client1.succeed("mkdir -p $HOME/.ssh")
-        client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
-        client1.succeed("chmod 0400 $HOME/.ssh/privk")
-        client1.succeed("git -C /tmp/repo init")
-        client1.succeed("echo hello world > /tmp/repo/testfile")
-        client1.succeed("git -C /tmp/repo add .")
-        client1.succeed("git config --global user.email test@localhost")
-        client1.succeed("git config --global user.name test")
-        client1.succeed("git -C /tmp/repo commit -m 'Initial import'")
-        client1.succeed(f"git -C /tmp/repo remote add origin {REPO}")
+        client.succeed("mkdir -p ~/.ssh")
+        client.succeed("(umask 0077; cat ${snakeOilPrivateKey} > ~/.ssh/privk)")
+
+        client.succeed("mkdir /tmp/repo")
+        client.succeed("git -C /tmp/repo init")
+        client.succeed("echo 'hello world' > /tmp/repo/testfile")
+        client.succeed("git -C /tmp/repo add .")
+        client.succeed("git -C /tmp/repo commit -m 'Initial import'")
+        client.succeed("git -C /tmp/repo remote add origin ${remoteUri}")
 
         server.wait_for_unit("forgejo.service")
         server.wait_for_open_port(3000)
@@ -109,9 +141,9 @@ let
         assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
 
         api_version = json.loads(server.succeed("curl http://localhost:3000/api/forgejo/v1/version")).get("version")
-        assert "development" != api_version and "-gitea-" in api_version, (
+        assert "development" != api_version and "${pkgs.forgejo.version}+gitea-" in api_version, (
             "/api/forgejo/v1/version should not return 'development' "
-            + f"but should contain a gitea compatibility version string. Got '{api_version}' instead."
+            + f"but should contain a forgejo+gitea compatibility version string. Got '{api_version}' instead."
         )
 
         server.succeed(
@@ -120,7 +152,7 @@ let
         )
         server.succeed(
             "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea admin user create "
-            + "--username test --password totallysafe --email test@localhost'"
+            + "--username test --password totallysafe --email test@localhost --must-change-password=false'"
         )
 
         api_token = server.succeed(
@@ -143,18 +175,14 @@ let
             + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
         )
 
-        client1.succeed(
-            f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master"
-        )
+        client.succeed("git -C /tmp/repo push origin main")
 
-        client2.succeed("mkdir -p $HOME/.ssh")
-        client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
-        client2.succeed("chmod 0400 $HOME/.ssh/privk")
-        client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}")
-        client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"')
+        client.succeed("git clone ${remoteUri} /tmp/repo-clone")
+        print(client.succeed("ls -lash /tmp/repo-clone"))
+        assert "hello world" == client.succeed("cat /tmp/repo-clone/testfile").strip()
 
         with subtest("Testing git protocol version=2 over ssh"):
-            git_protocol = client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' GIT_TRACE2_EVENT=true git -C repo fetch |& grep negotiated-version")
+            git_protocol = client.succeed("GIT_TRACE2_EVENT=true git -C /tmp/repo-clone fetch |& grep negotiated-version")
             version = json.loads(git_protocol).get("value")
             assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead."
 
@@ -164,7 +192,7 @@ let
             timeout=10
         )
 
-        with subtest("Testing runner registration"):
+        with subtest("Testing runner registration and action workflow"):
             server.succeed(
                 "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
             )
@@ -172,6 +200,52 @@ let
             server.wait_for_unit("gitea-runner-test.service")
             server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
 
+            # enable actions feature for this repository, defaults to disabled
+            server.succeed(
+                "curl --fail -X PATCH http://localhost:3000/api/v1/repos/test/repo "
+                + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+                + f"-H 'Authorization: token {api_token}'"
+                + ' -d \'{"has_actions":true}\'''
+            )
+
+            # mirror "actions/checkout" action
+            client.succeed("cp -R ${checkoutActionSource}/ /tmp/checkout")
+            client.succeed("git -C /tmp/checkout init")
+            client.succeed("git -C /tmp/checkout add .")
+            client.succeed("git -C /tmp/checkout commit -m 'Initial import'")
+            client.succeed("git -C /tmp/checkout remote add origin ${remoteUriCheckoutAction}")
+            client.succeed("git -C /tmp/checkout push origin main")
+
+            # push workflow to initial repo
+            client.succeed("mkdir -p /tmp/repo/.forgejo/workflows")
+            client.succeed("cp ${pkgs.writeText "dummy-workflow.yml" actionsWorkflowYaml} /tmp/repo/.forgejo/workflows/")
+            client.succeed("git -C /tmp/repo add .")
+            client.succeed("git -C /tmp/repo commit -m 'Add dummy workflow'")
+            client.succeed("git -C /tmp/repo push origin main")
+
+            def poll_workflow_action_status(_) -> bool:
+                output = server.succeed(
+                    "curl --fail http://localhost:3000/test/repo/actions | "
+                    + 'htmlq ".flex-item-leading span" --attribute "data-tooltip-content"'
+                ).strip()
+
+                # values taken from https://codeberg.org/forgejo/forgejo/src/commit/af47c583b4fb3190fa4c4c414500f9941cc02389/options/locale/locale_en-US.ini#L3649-L3661
+                if output in [ "Failure", "Canceled", "Skipped", "Blocked" ]:
+                    raise Exception(f"Workflow status is '{output}', which we consider failed.")
+                    server.log(f"Command returned '{output}', which we consider failed.")
+
+                elif output in [ "Unknown", "Waiting", "Running", "" ]:
+                    server.log(f"Workflow status is '{output}'. Waiting some more...")
+                    return False
+
+                elif output in [ "Success" ]:
+                    return True
+
+                raise Exception(f"Workflow status is '{output}', which we don't know. Value mappings likely need updating.")
+
+            with server.nested("Waiting for the workflow run to be successful"):
+                retry(poll_workflow_action_status)
+
         with subtest("Testing backup service"):
             server.succeed("${serverSystem}/specialisation/dump/bin/switch-to-configuration test")
             server.systemctl("start forgejo-dump")
@@ -181,4 +255,4 @@ let
   });
 in
 
-listToAttrs (map makeGForgejoTest supportedDbTypes)
+listToAttrs (map makeForgejoTest supportedDbTypes)
diff --git a/nixos/tests/freshrss-none-auth.nix b/nixos/tests/freshrss-none-auth.nix
new file mode 100644
index 0000000000000..fd63470386a05
--- /dev/null
+++ b/nixos/tests/freshrss-none-auth.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ mattchrist ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      authType = "none";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s http://127.0.0.1:80/i/")
+    assert '<title>Main stream · FreshRSS</title>' in response, "FreshRSS stream page didn't load successfully"
+  '';
+})
diff --git a/nixos/tests/gnome-extensions.nix b/nixos/tests/gnome-extensions.nix
index a9bb5e3766b7b..51ccabd7e6a65 100644
--- a/nixos/tests/gnome-extensions.nix
+++ b/nixos/tests/gnome-extensions.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix (
 { pkgs, lib, ...}:
 {
   name = "gnome-extensions";
-  meta.maintainers = [ lib.maintainers.piegames ];
+  meta.maintainers = [ ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixos/tests/gonic.nix b/nixos/tests/gonic.nix
index 726d7da0970f7..adf0f511a9cf7 100644
--- a/nixos/tests/gonic.nix
+++ b/nixos/tests/gonic.nix
@@ -2,11 +2,19 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "gonic";
 
   nodes.machine = { ... }: {
+    systemd.tmpfiles.settings = {
+      "10-gonic" = {
+        "/tmp/music"."d" = {};
+        "/tmp/podcast"."d" = {};
+        "/tmp/playlists"."d" = {};
+      };
+    };
     services.gonic = {
       enable = true;
       settings = {
-        music-path = [ "/tmp" ];
-        podcast-path = "/tmp";
+        music-path = [ "/tmp/music" ];
+        podcast-path = "/tmp/podcast";
+        playlists-path = "/tmp/playlists";
       };
     };
   };
diff --git a/nixos/tests/gvisor.nix b/nixos/tests/gvisor.nix
index 7f130b709fc9d..5c9447b07118d 100644
--- a/nixos/tests/gvisor.nix
+++ b/nixos/tests/gvisor.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "gvisor";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ andrew-d ];
+    maintainers = [ ];
   };
 
   nodes = {
diff --git a/nixos/tests/incus/virtual-machine.nix b/nixos/tests/incus/virtual-machine.nix
index ab378c7b9490e..eebbbd113ed16 100644
--- a/nixos/tests/incus/virtual-machine.nix
+++ b/nixos/tests/incus/virtual-machine.nix
@@ -30,6 +30,9 @@ in
       memorySize = 1024;
       diskSize = 4096;
 
+      # Provide a TPM to test vTPM support for guests
+      tpm.enable = true;
+
       incus.enable = true;
     };
     networking.nftables.enable = true;
@@ -47,8 +50,14 @@ in
     with subtest("virtual-machine image can be imported"):
         machine.succeed("incus image import ${vm-image-metadata}/*/*.tar.xz ${vm-image-disk}/nixos.qcow2 --alias nixos")
 
+    with subtest("virtual-machine can be created"):
+        machine.succeed("incus create nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+
+    with subtest("virtual tpm can be configured"):
+        machine.succeed("incus config device add ${instance-name} vtpm tpm path=/dev/tpm0")
+
     with subtest("virtual-machine can be launched and become available"):
-        machine.succeed("incus launch nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+        machine.succeed("incus start ${instance-name}")
         with machine.nested("Waiting for instance to start and be usable"):
           retry(instance_is_up)
 
@@ -57,5 +66,14 @@ in
 
     with subtest("lxd-agent has a valid path"):
         machine.succeed("incus exec ${instance-name} -- bash -c 'true'")
+
+    with subtest("guest supports cpu hotplug"):
+        machine.succeed("incus config set ${instance-name} limits.cpu=1")
+        count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
+        assert count == 1, f"Wrong number of CPUs reported, want: 1, got: {count}"
+
+        machine.succeed("incus config set ${instance-name} limits.cpu=2")
+        count = int(machine.succeed("incus exec ${instance-name} -- nproc").strip())
+        assert count == 2, f"Wrong number of CPUs reported, want: 2, got: {count}"
   '';
 })
diff --git a/nixos/tests/isolate.nix b/nixos/tests/isolate.nix
new file mode 100644
index 0000000000000..327231be1cd4a
--- /dev/null
+++ b/nixos/tests/isolate.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "isolate";
+  meta.maintainers = with lib.maintainers; [ virchau13 ];
+
+  nodes.machine =
+    { ... }:
+    {
+      security.isolate = {
+        enable = true;
+      };
+    };
+
+  testScript = ''
+    bash_path = machine.succeed('realpath $(which bash)').strip()
+    sleep_path = machine.succeed('realpath $(which sleep)').strip()
+    def sleep_test(walltime, sleeptime):
+        return f'isolate --no-default-dirs --wall-time {walltime} ' + \
+            f'--dir=/box={box_path} --dir=/nix=/nix --run -- ' + \
+            f"{bash_path} -c 'exec -a sleep {sleep_path} {sleeptime}'"
+
+    def sleep_test_cg(walltime, sleeptime):
+        return f'isolate --cg --no-default-dirs --wall-time {walltime} ' + \
+            f'--dir=/box={box_path} --dir=/nix=/nix --processes=2 --run -- ' + \
+            f"{bash_path} -c '( exec -a sleep {sleep_path} {sleeptime} )'"
+
+    with subtest("without cgroups"):
+        box_path = machine.succeed('isolate --init').strip()
+        machine.succeed(sleep_test(1, 0.5))
+        machine.fail(sleep_test(0.5, 1))
+        machine.succeed('isolate --cleanup')
+    with subtest("with cgroups"):
+        box_path = machine.succeed('isolate --cg --init').strip()
+        machine.succeed(sleep_test_cg(1, 0.5))
+        machine.fail(sleep_test_cg(0.5, 1))
+        machine.succeed('isolate --cg --cleanup')
+  '';
+})
diff --git a/nixos/tests/libinput.nix b/nixos/tests/libinput.nix
index 9b6fa159b999c..b002492b16657 100644
--- a/nixos/tests/libinput.nix
+++ b/nixos/tests/libinput.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ ... }:
 
       test-support.displayManager.auto.user = "alice";
 
-      services.xserver.libinput = {
+      services.libinput = {
         enable = true;
         mouse = {
           naturalScrolling = true;
diff --git a/nixos/tests/lomiri.nix b/nixos/tests/lomiri.nix
index c1e777873b08f..9d6337e9977cb 100644
--- a/nixos/tests/lomiri.nix
+++ b/nixos/tests/lomiri.nix
@@ -253,22 +253,35 @@ in {
     with subtest("ayatana indicators work"):
         open_starter()
         machine.send_chars("Indicators\n")
-        machine.wait_for_text(r"(Indicators|Client|List|datetime|session)")
+        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
         machine.screenshot("indicators_open")
 
         # Element tab order within the indicator menus is not fully deterministic
         # Only check that the indicators are listed & their items load
 
+        with subtest("lomiri indicator network works"):
+            # Select indicator-network
+            machine.send_key("tab")
+            # Don't go further down, first entry
+            machine.send_key("ret")
+            machine.wait_for_text(r"(Flight|Wi-Fi)")
+            machine.screenshot("indicators_network")
+
+        machine.send_key("shift-tab")
+        machine.send_key("ret")
+        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
+
         with subtest("ayatana indicator datetime works"):
             # Select ayatana-indicator-datetime
             machine.send_key("tab")
+            machine.send_key("down")
             machine.send_key("ret")
             machine.wait_for_text("Time and Date Settings")
             machine.screenshot("indicators_timedate")
 
         machine.send_key("shift-tab")
         machine.send_key("ret")
-        machine.wait_for_text(r"(Indicators|Client|List|datetime|session)")
+        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
 
         with subtest("ayatana indicator session works"):
             # Select ayatana-indicator-session
diff --git a/nixos/tests/networking/networkmanager.nix b/nixos/tests/networking/networkmanager.nix
new file mode 100644
index 0000000000000..e654e37d7efb7
--- /dev/null
+++ b/nixos/tests/networking/networkmanager.nix
@@ -0,0 +1,172 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  lib = pkgs.lib;
+  # this is intended as a client test since you shouldn't use NetworkManager for a router or server
+  # so using systemd-networkd for the router vm is fine in these tests.
+  router = import ./router.nix { networkd = true; };
+  qemu-common = import ../../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+  clientConfig = extraConfig: lib.recursiveUpdate {
+    networking.useDHCP = false;
+
+    # Make sure that only NetworkManager configures the interface
+    networking.interfaces = lib.mkForce {
+      eth1 = {};
+    };
+    networking.networkmanager = {
+      enable = true;
+      # this is needed so NM doesn't generate 'Wired Connection' profiles and instead uses the default one
+      settings.main.no-auto-default = "*";
+      ensureProfiles.profiles.default = {
+        connection = {
+          id = "default";
+          type = "ethernet";
+          interface-name = "eth1";
+          autoconnect = true;
+        };
+      };
+    };
+  } extraConfig;
+  testCases = {
+    static = {
+      name = "static";
+      nodes = {
+        inherit router;
+        client = clientConfig {
+          networking.networkmanager.ensureProfiles.profiles.default = {
+            ipv4.method = "manual";
+            ipv4.addresses = "192.168.1.42/24";
+            ipv4.gateway = "192.168.1.1";
+            ipv6.method = "manual";
+            ipv6.addresses = "fd00:1234:5678:1::42/64";
+            ipv6.gateway = "fd00:1234:5678:1::1";
+          };
+        };
+      };
+      testScript = ''
+        start_all()
+        router.systemctl("start network-online.target")
+        router.wait_for_unit("network-online.target")
+        client.wait_for_unit("NetworkManager.service")
+
+        with subtest("Wait until we have an ip address on each interface"):
+            client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+            client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+
+        with subtest("Test if icmp echo works"):
+            client.wait_until_succeeds("ping -c 1 192.168.3.1")
+            client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
+            router.wait_until_succeeds("ping -c 1 192.168.1.42")
+            router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::42")
+      '';
+    };
+    auto = {
+      name = "auto";
+      nodes = {
+        inherit router;
+        client = clientConfig {
+          networking.networkmanager.ensureProfiles.profiles.default = {
+            ipv4.method = "auto";
+            ipv6.method = "auto";
+          };
+        };
+      };
+      testScript = ''
+        start_all()
+        router.systemctl("start network-online.target")
+        router.wait_for_unit("network-online.target")
+        client.wait_for_unit("NetworkManager.service")
+
+        with subtest("Wait until we have an ip address on each interface"):
+            client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+            client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+
+        with subtest("Test if icmp echo works"):
+            client.wait_until_succeeds("ping -c 1 192.168.1.1")
+            client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+            router.wait_until_succeeds("ping -c 1 192.168.1.2")
+            router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
+      '';
+    };
+    dns = {
+      name = "dns";
+      nodes = {
+        inherit router;
+        dynamic = clientConfig {
+          networking.networkmanager.ensureProfiles.profiles.default = {
+            ipv4.method = "auto";
+          };
+        };
+        static = clientConfig {
+          networking.networkmanager.ensureProfiles.profiles.default = {
+            ipv4 = {
+              method = "auto";
+              ignore-auto-dns = "true";
+              dns = "10.10.10.10";
+              dns-search = "";
+            };
+          };
+        };
+      };
+      testScript = ''
+        start_all()
+        router.systemctl("start network-online.target")
+        router.wait_for_unit("network-online.target")
+        dynamic.wait_for_unit("NetworkManager.service")
+        static.wait_for_unit("NetworkManager.service")
+
+        dynamic.wait_until_succeeds("cat /etc/resolv.conf | grep -q '192.168.1.1'")
+        static.wait_until_succeeds("cat /etc/resolv.conf | grep -q '10.10.10.10'")
+        static.wait_until_fails("cat /etc/resolv.conf | grep -q '192.168.1.1'")
+      '';
+    };
+    dispatcherScripts = {
+      name = "dispatcherScripts";
+      nodes.client = clientConfig {
+        networking.networkmanager.dispatcherScripts = [{
+          type = "pre-up";
+          source = pkgs.writeText "testHook" ''
+            touch /tmp/dispatcher-scripts-are-working
+          '';
+        }];
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("NetworkManager.service")
+        client.wait_until_succeeds("stat /tmp/dispatcher-scripts-are-working")
+      '';
+    };
+    envsubst = {
+      name = "envsubst";
+      nodes.client = let
+        # you should never write secrets in to your nixos configuration, please use tools like sops-nix or agenix
+        secretFile = pkgs.writeText "my-secret.env" ''
+          MY_SECRET_IP=fd00:1234:5678:1::23/64
+        '';
+      in clientConfig {
+        networking.networkmanager.ensureProfiles.environmentFiles = [ secretFile ];
+        networking.networkmanager.ensureProfiles.profiles.default = {
+          ipv6.method = "manual";
+          ipv6.addresses = "$MY_SECRET_IP";
+        };
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("NetworkManager.service")
+        client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+        client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::23")
+      '';
+    };
+  };
+in lib.mapAttrs (lib.const (attrs: makeTest (attrs // {
+  name = "${attrs.name}-Networking-NetworkManager";
+  meta = {
+    maintainers = with lib.maintainers; [ janik ];
+  };
+
+}))) testCases
diff --git a/nixos/tests/nginx-sso.nix b/nixos/tests/nginx-sso.nix
index 221c5f4ed9058..2bb9c7a1c3bb7 100644
--- a/nixos/tests/nginx-sso.nix
+++ b/nixos/tests/nginx-sso.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "nginx-sso";
   meta = {
-    maintainers = with pkgs.lib.maintainers; [ delroth ];
+    maintainers = with pkgs.lib.maintainers; [ ambroisie ];
   };
 
   nodes.machine = {
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index 8074fd2ed4838..a039986621cab 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -22,6 +22,19 @@ in {
         ];
       };
 
+    server-allowed-users =
+      { ... }:
+
+      {
+        services.openssh = { enable = true; settings.AllowUsers = [ "alice" "bob" ]; };
+        users.groups = { alice = { }; bob = { }; carol = { }; };
+        users.users = {
+          alice = { isNormalUser = true; group = "alice"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          bob = { isNormalUser = true; group = "bob"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          carol = { isNormalUser = true; group = "carol"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+        };
+      };
+
     server-lazy =
       { ... }:
 
@@ -95,17 +108,21 @@ in {
         };
       };
 
-    server_allowedusers =
-      { ... }:
-
+    server-no-pam =
+      { pkgs, ... }:
       {
-        services.openssh = { enable = true; settings.AllowUsers = [ "alice" "bob" ]; };
-        users.groups = { alice = { }; bob = { }; carol = { }; };
-        users.users = {
-          alice = { isNormalUser = true; group = "alice"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
-          bob = { isNormalUser = true; group = "bob"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
-          carol = { isNormalUser = true; group = "carol"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+        programs.ssh.package = pkgs.opensshPackages.openssh.override {
+          withPAM = false;
+        };
+        services.openssh = {
+          enable = true;
+          settings = {
+            UsePAM = false;
+          };
         };
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
       };
 
     client =
@@ -119,8 +136,10 @@ in {
     start_all()
 
     server.wait_for_unit("sshd", timeout=30)
+    server_allowed_users.wait_for_unit("sshd", timeout=30)
     server_localhost_only.wait_for_unit("sshd", timeout=30)
     server_match_rule.wait_for_unit("sshd", timeout=30)
+    server_no_pam.wait_for_unit("sshd", timeout=30)
 
     server_lazy.wait_for_unit("sshd.socket", timeout=30)
     server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30)
@@ -166,8 +185,9 @@ in {
             "cat ${snakeOilPrivateKey} > privkey.snakeoil"
         )
         client.succeed("chmod 600 privkey.snakeoil")
+        # The final segment in this IP is allocated according to the alphabetical order of machines in this test.
         client.succeed(
-            "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.4 true",
+            "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.5 true",
             timeout=30
         )
 
@@ -198,15 +218,25 @@ in {
         )
         client.succeed("chmod 600 privkey.snakeoil")
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server_allowedusers true",
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server-allowed-users true",
             timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server_allowedusers true",
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server-allowed-users true",
             timeout=30
         )
         client.fail(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server_allowedusers true",
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server-allowed-users true",
+            timeout=30
+        )
+
+    with subtest("no-pam"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-pam true",
             timeout=30
         )
   '';
diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix
index 3d834b29958de..3ef291ba7e06f 100644
--- a/nixos/tests/paperless.nix
+++ b/nixos/tests/paperless.nix
@@ -23,6 +23,7 @@ import ./make-test-python.nix ({ lib, ... }: {
       };
       services.paperless.settings = {
         PAPERLESS_DBHOST = "/run/postgresql";
+        PAPERLESS_OCR_LANGUAGE = "deu";
       };
     };
   }; in self;
diff --git a/nixos/tests/phosh.nix b/nixos/tests/phosh.nix
index 78d6da31beee1..d505f0ffc5245 100644
--- a/nixos/tests/phosh.nix
+++ b/nixos/tests/phosh.nix
@@ -25,6 +25,10 @@ in {
         };
       };
 
+      environment.systemPackages = [
+        pkgs.phosh-mobile-settings
+      ];
+
       systemd.services.phosh = {
         environment = {
           # Accelerated graphics fail on phoc 0.20 (wlroots 0.15)
@@ -63,8 +67,13 @@ in {
         phone.screenshot("03launcher")
 
     with subtest("Check the on-screen keyboard shows"):
-        phone.send_chars("setting", delay=0.2)
+        phone.send_chars("mobile setting", delay=0.2)
         phone.wait_for_text("123") # A button on the OSK
         phone.screenshot("04osk")
+
+    with subtest("Check mobile-phosh-settings starts"):
+       phone.send_chars("\n")
+       phone.wait_for_text("Tweak advanced mobile settings");
+       phone.screenshot("05settings")
   '';
 })
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index 51d5e8ae59b92..9ac4f8211e6b1 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -5,7 +5,7 @@
 
 let
   inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
-  testCombinations = pkgs.lib.cartesianProductOfSets {
+  testCombinations = pkgs.lib.cartesianProduct {
     predictable = [true false];
     withNetworkd = [true false];
     systemdStage1 = [true false];
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 3dc368e320ff2..56569c4de2c85 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -227,6 +227,54 @@ let
       '';
     };
 
+    dnssec = {
+      exporterConfig = {
+        enable = true;
+        configuration = {
+          records = [
+            {
+              zone = "example.com";
+              record = "@";
+              type = "SOA";
+            }
+          ];
+        };
+        resolvers = [ "127.0.0.1:53" ];
+      };
+      metricProvider = {
+        services.knot = {
+          enable = true;
+          settingsFile = pkgs.writeText "knot.conf" ''
+            server:
+              listen: 127.0.0.1@53
+            template:
+              - id: default
+                storage: ${pkgs.buildEnv {
+                  name = "zones";
+                  paths = [(pkgs.writeTextDir "example.com.zone" ''
+                    @ SOA ns1.example.com. noc.example.com. 2024032401 86400 7200 3600000 172800
+                    @       NS      ns1
+                    ns1     A       192.168.0.1
+                  '')];
+                }}
+                zonefile-load: difference
+                zonefile-sync: -1
+            zone:
+              - domain: example.com
+                file: example.com.zone
+                dnssec-signing: on
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("knot.service")
+        wait_for_open_port(53)
+        wait_for_unit("prometheus-dnssec-exporter.service")
+        wait_for_open_port(9204)
+        succeed("curl -sSf http://localhost:9204/metrics | grep 'example.com'")
+      '';
+    };
+
     # 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.
@@ -859,7 +907,7 @@ let
               attrs = {
                 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
                 olcDatabase = "{1}mdb";
-                olcDbDirectory = "/var/db/openldap";
+                olcDbDirectory = "/var/lib/openldap/db";
                 olcSuffix = "dc=example";
                 olcRootDN = {
                   # cn=root,dc=example
diff --git a/nixos/tests/promscale.nix b/nixos/tests/promscale.nix
deleted file mode 100644
index da18628f2482c..0000000000000
--- a/nixos/tests/promscale.nix
+++ /dev/null
@@ -1,60 +0,0 @@
-# mostly copied from ./timescaledb.nix which was copied from ./postgresql.nix
-# as it seemed unapproriate to test additional extensions for postgresql there.
-
-{ system ? builtins.currentSystem
-, config ? { }
-, pkgs ? import ../.. { inherit system config; }
-}:
-
-with import ../lib/testing-python.nix { inherit system pkgs; };
-with pkgs.lib;
-
-let
-  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
-  test-sql = pkgs.writeText "postgresql-test" ''
-    CREATE USER promscale SUPERUSER PASSWORD 'promscale';
-    CREATE DATABASE promscale OWNER promscale;
-  '';
-
-  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
-    name = postgresql-name;
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ anpin ];
-    };
-
-    nodes.machine = { config, pkgs, ... }:
-      {
-        services.postgresql = {
-          enable = true;
-          package = postgresql-package;
-          extraPlugins = ps: with ps; [
-            timescaledb
-            promscale_extension
-          ];
-          settings = { shared_preload_libraries = "timescaledb, promscale"; };
-        };
-        environment.systemPackages = with pkgs; [ promscale ];
-      };
-
-    testScript = ''
-      machine.start()
-      machine.wait_for_unit("postgresql")
-      with subtest("Postgresql with extensions timescaledb and promscale is available just after unit start"):
-          print(machine.succeed("sudo -u postgres psql -f ${test-sql}"))
-          machine.succeed("sudo -u postgres psql promscale -c 'SHOW shared_preload_libraries;' | grep promscale")
-          machine.succeed(
-            "promscale --db.name promscale --db.password promscale --db.user promscale --db.ssl-mode allow --startup.install-extensions --startup.only"
-          )
-      machine.succeed("sudo -u postgres psql promscale -c 'SELECT ps_trace.get_trace_retention_period();' | grep '(1 row)'")
-      machine.shutdown()
-    '';
-  };
-  #version 15 is not supported yet
-  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12" && !(versionAtLeast value.version "15")) postgresql-versions;
-in
-mapAttrs'
-  (name: package: {
-    inherit name;
-    value = make-postgresql-test name package;
-  })
-  applicablePostgresqlVersions
diff --git a/nixos/tests/qtile.nix b/nixos/tests/qtile.nix
new file mode 100644
index 0000000000000..b4d8f9d421144
--- /dev/null
+++ b/nixos/tests/qtile.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ lib, ...} : {
+  name = "qtile";
+
+  meta = {
+    maintainers = with lib.maintainers; [ sigmanificient ];
+  };
+
+  nodes.machine = { pkgs, lib, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    test-support.displayManager.auto.user = "alice";
+
+    services.xserver.windowManager.qtile.enable = true;
+    services.displayManager.defaultSession = lib.mkForce "none+qtile";
+
+    environment.systemPackages = [ pkgs.kitty ];
+  };
+
+  testScript = ''
+    with subtest("ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("/home/alice/.Xauthority")
+        machine.succeed("xauth merge ~alice/.Xauthority")
+
+    with subtest("ensure client is available"):
+        machine.succeed("qtile --version")
+
+    with subtest("ensure we can open a new terminal"):
+        machine.sleep(2)
+        machine.send_key("meta_l-ret")
+        machine.wait_for_window(r"alice.*?machine")
+        machine.sleep(2)
+        machine.screenshot("terminal")
+  '';
+})
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index 66650dce4a008..868b28085a675 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -6,7 +6,7 @@ let
   port = "5232";
   filesystem_folder = "/data/radicale";
 
-  cli = "${pkgs.calendar-cli}/bin/calendar-cli --caldav-user ${user} --caldav-pass ${password}";
+  cli = "${lib.getExe pkgs.calendar-cli} --caldav-user ${user} --caldav-pass ${password}";
 in {
   name = "radicale3";
   meta.maintainers = with lib.maintainers; [ dotlambda ];
diff --git a/nixos/tests/sunshine.nix b/nixos/tests/sunshine.nix
new file mode 100644
index 0000000000000..7c7e86de203a0
--- /dev/null
+++ b/nixos/tests/sunshine.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sunshine";
+  meta = {
+    # test is flaky on aarch64
+    broken = pkgs.stdenv.isAarch64;
+    maintainers = [ lib.maintainers.devusb ];
+  };
+
+  nodes.sunshine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.sunshine = {
+      enable = true;
+      openFirewall = true;
+      settings = {
+        capture = "x11";
+        encoder = "software";
+        output_name = 0;
+      };
+    };
+
+    environment.systemPackages = with pkgs; [
+      gxmessage
+    ];
+
+  };
+
+  nodes.moonlight = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [
+      moonlight-qt
+    ];
+
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    # start the tests, wait for sunshine to be up
+    start_all()
+    sunshine.wait_for_open_port(48010,"localhost")
+
+    # set the admin username/password, restart sunshine
+    sunshine.execute("sunshine --creds sunshine sunshine")
+    sunshine.systemctl("restart sunshine","root")
+    sunshine.wait_for_open_port(48010,"localhost")
+
+    # initiate pairing from moonlight
+    moonlight.execute("moonlight pair sunshine --pin 1234 >&2 & disown")
+    moonlight.wait_for_console_text("Executing request")
+
+    # respond to pairing request from sunshine
+    sunshine.succeed("curl --insecure -u sunshine:sunshine -d '{\"pin\": \"1234\"}' https://localhost:47990/api/pin")
+
+    # close moonlight once pairing complete
+    moonlight.send_key("kp_enter")
+
+    # put words on the sunshine screen for moonlight to see
+    sunshine.execute("gxmessage 'hello world' -center -font 'sans 75' >&2 & disown")
+
+    # connect to sunshine from moonlight and look for the words
+    moonlight.execute("moonlight --video-decoder software stream sunshine 'Desktop' >&2 & disown")
+    moonlight.wait_for_text("hello world")
+  '';
+})
diff --git a/nixos/tests/swayfx.nix b/nixos/tests/swayfx.nix
new file mode 100644
index 0000000000000..77844ec80ae1d
--- /dev/null
+++ b/nixos/tests/swayfx.nix
@@ -0,0 +1,207 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  {
+    name = "swayfx";
+    meta = {
+      maintainers = with lib.maintainers; [ eclairevoyant ];
+    };
+
+    # testScriptWithTypes:49: error: Cannot call function of unknown type
+    #           (machine.succeed if succeed else machine.execute)(
+    #           ^
+    # Found 1 error in 1 file (checked 1 source file)
+    skipTypeCheck = true;
+
+    nodes.machine =
+      { config, ... }:
+      {
+        # Automatically login on tty1 as a normal user:
+        imports = [ ./common/user-account.nix ];
+        services.getty.autologinUser = "alice";
+
+        environment = {
+          # For glinfo and wayland-info:
+          systemPackages = with pkgs; [
+            mesa-demos
+            wayland-utils
+            alacritty
+          ];
+          # Use a fixed SWAYSOCK path (for swaymsg):
+          variables = {
+            "SWAYSOCK" = "/tmp/sway-ipc.sock";
+            # TODO: Investigate if we can get hardware acceleration to work (via
+            # virtio-gpu and Virgil). We currently have to use the Pixman software
+            # renderer since the GLES2 renderer doesn't work inside the VM (even
+            # with WLR_RENDERER_ALLOW_SOFTWARE):
+            # "WLR_RENDERER_ALLOW_SOFTWARE" = "1";
+            "WLR_RENDERER" = "pixman";
+          };
+          # For convenience:
+          shellAliases = {
+            test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+            test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+          };
+
+          # To help with OCR:
+          etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+            main = {
+              font = "inconsolata:size=14";
+            };
+            colors = rec {
+              foreground = "000000";
+              background = "ffffff";
+              regular2 = foreground;
+            };
+          };
+
+          etc."gpg-agent.conf".text = ''
+            pinentry-timeout 86400
+          '';
+        };
+
+        fonts.packages = [ pkgs.inconsolata ];
+
+        # Automatically configure and start Sway when logging in on tty1:
+        programs.bash.loginShellInit = ''
+          if [ "$(tty)" = "/dev/tty1" ]; then
+            set -e
+
+            mkdir -p ~/.config/sway
+            sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
+
+            sway --validate
+            sway && touch /tmp/sway-exit-ok
+          fi
+        '';
+
+        programs.sway = {
+          enable = true;
+          package = pkgs.swayfx.override { isNixOS = true; };
+        };
+
+        # To test pinentry via gpg-agent:
+        programs.gnupg.agent.enable = true;
+
+        # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
+        virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+      };
+
+    testScript =
+      { nodes, ... }:
+      ''
+        import shlex
+        import json
+
+        q = shlex.quote
+        NODE_GROUPS = ["nodes", "floating_nodes"]
+
+
+        def swaymsg(command: str = "", succeed=True, type="command"):
+            assert command != "" or type != "command", "Must specify command or type"
+            shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
+            with machine.nested(
+                f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
+            ):
+                ret = (machine.succeed if succeed else machine.execute)(
+                    f"su - alice -c {shell}"
+                )
+
+            # execute also returns a status code, but disregard.
+            if not succeed:
+                _, ret = ret
+
+            if not succeed and not ret:
+                return None
+
+            parsed = json.loads(ret)
+            return parsed
+
+
+        def walk(tree):
+            yield tree
+            for group in NODE_GROUPS:
+                for node in tree.get(group, []):
+                    yield from walk(node)
+
+
+        def wait_for_window(pattern):
+            def func(last_chance):
+                nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
+
+                if last_chance:
+                    nodes = list(nodes)
+                    machine.log(f"Last call! Current list of windows: {nodes}")
+
+                return any(pattern in name for name in nodes)
+
+            retry(func)
+
+        start_all()
+        machine.wait_for_unit("multi-user.target")
+
+        # To check the version:
+        print(machine.succeed("sway --version"))
+
+        # Wait for Sway to complete startup:
+        machine.wait_for_file("/run/user/1000/wayland-1")
+        machine.wait_for_file("/tmp/sway-ipc.sock")
+
+        # Test XWayland (foot does not support X):
+        swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty")
+        wait_for_window("alice@machine")
+        machine.send_chars("test-x11\n")
+        machine.wait_for_file("/tmp/test-x11-exit-ok")
+        print(machine.succeed("cat /tmp/test-x11.out"))
+        machine.copy_from_vm("/tmp/test-x11.out")
+        machine.screenshot("alacritty_glinfo")
+        machine.succeed("pkill alacritty")
+
+        # Start a terminal (foot) on workspace 3:
+        machine.send_key("alt-3")
+        machine.sleep(3)
+        machine.send_key("alt-ret")
+        wait_for_window("alice@machine")
+        machine.send_chars("test-wayland\n")
+        machine.wait_for_file("/tmp/test-wayland-exit-ok")
+        print(machine.succeed("cat /tmp/test-wayland.out"))
+        machine.copy_from_vm("/tmp/test-wayland.out")
+        machine.screenshot("foot_wayland_info")
+        machine.send_key("alt-shift-q")
+        machine.wait_until_fails("pgrep foot")
+
+        # Test gpg-agent starting pinentry-gnome3 via D-Bus (tests if
+        # $WAYLAND_DISPLAY is correctly imported into the D-Bus user env):
+        swaymsg("exec mkdir -p ~/.gnupg")
+        swaymsg("exec cp /etc/gpg-agent.conf ~/.gnupg")
+
+        swaymsg("exec DISPLAY=INVALID gpg --no-tty --yes --quick-generate-key test", succeed=False)
+        machine.wait_until_succeeds("pgrep --exact gpg")
+        wait_for_window("gpg")
+        machine.succeed("pgrep --exact gpg")
+        machine.screenshot("gpg_pinentry")
+        machine.send_key("alt-shift-q")
+        machine.wait_until_fails("pgrep --exact gpg")
+
+        # Test swaynag:
+        def get_height():
+            return [node['rect']['height'] for node in walk(swaymsg(type="get_tree")) if node['focused']][0]
+
+        before = get_height()
+        machine.send_key("alt-shift-e")
+        retry(lambda _: get_height() < before)
+        machine.screenshot("sway_exit")
+
+        swaymsg("exec swaylock")
+        machine.wait_until_succeeds("pgrep -x swaylock")
+        machine.sleep(3)
+        machine.send_chars("${nodes.machine.config.users.users.alice.password}")
+        machine.send_key("ret")
+        machine.wait_until_fails("pgrep -x swaylock")
+
+        # Exit Sway and verify process exit status 0:
+        swaymsg("exit", succeed=False)
+        machine.wait_until_fails("pgrep -x sway")
+        machine.wait_for_file("/tmp/sway-exit-ok")
+      '';
+  }
+)
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index a57d66f82eac9..4a7bcd5a82264 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -610,6 +610,11 @@ in {
     # Returns a comma separated representation of the given list in sorted
     # order, that matches the output format of switch-to-configuration.pl
     sortedUnits = xs: lib.concatStringsSep ", " (builtins.sort builtins.lessThan xs);
+
+    dbusService = {
+      "dbus" = "dbus.service";
+      "broker" = "dbus-broker.service";
+    }.${nodes.machine.services.dbus.implementation};
   in /* python */ ''
     def switch_to_specialisation(system, name, action="test", fail=False):
         if name == "":
@@ -691,9 +696,9 @@ in {
     with subtest("continuing from an aborted switch"):
         # An aborted switch will write into a file what it tried to start
         # and a second switch should continue from this
-        machine.succeed("echo dbus-broker.service > /run/nixos/start-list")
+        machine.succeed("echo ${dbusService} > /run/nixos/start-list")
         out = switch_to_specialisation("${machine}", "modifiedSystemConf")
-        assert_contains(out, "starting the following units: dbus-broker.service\n")
+        assert_contains(out, "starting the following units: ${dbusService}\n")
 
     with subtest("fstab mounts"):
         switch_to_specialisation("${machine}", "")
@@ -732,7 +737,7 @@ in {
         out = switch_to_specialisation("${machine}", "")
         assert_contains(out, "stopping the following units: test.mount\n")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -740,7 +745,7 @@ in {
         out = switch_to_specialisation("${machine}", "storeMountModified")
         assert_lacks(out, "stopping the following units:")
         assert_contains(out, "NOT restarting the following changed units: -.mount")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -751,7 +756,7 @@ in {
         out = switch_to_specialisation("${machine}", "swap")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: swapfile.swap")
@@ -760,7 +765,7 @@ in {
         assert_contains(out, "stopping swap device: /swapfile")
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")
+        assert_contains(out, "reloading the following units: ${dbusService}\n")
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
@@ -781,7 +786,7 @@ in {
         assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")  # huh
+        assert_contains(out, "reloading the following units: ${dbusService}\n")  # huh
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: test.service\n")
@@ -858,7 +863,7 @@ in {
         assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
         assert_lacks(out, "stopping the following units:")
         assert_lacks(out, "NOT restarting the following changed units:")
-        assert_contains(out, "reloading the following units: dbus-broker.service\n")  # huh
+        assert_contains(out, "reloading the following units: ${dbusService}\n")  # huh
         assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_contains(out, "the following new units were started: test.service\n")
diff --git a/nixos/tests/syncthing-relay.nix b/nixos/tests/syncthing-relay.nix
index 3d70b1eda7b2a..cab9bcafe9d5c 100644
--- a/nixos/tests/syncthing-relay.nix
+++ b/nixos/tests/syncthing-relay.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "syncthing-relay";
-  meta.maintainers = with pkgs.lib.maintainers; [ delroth ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes.machine = {
     environment.systemPackages = [ pkgs.jq ];
diff --git a/nixos/tests/systemd-networkd-bridge.nix b/nixos/tests/systemd-networkd-bridge.nix
new file mode 100644
index 0000000000000..f1f8823e84205
--- /dev/null
+++ b/nixos/tests/systemd-networkd-bridge.nix
@@ -0,0 +1,103 @@
+/* This test ensures that we can configure spanning-tree protocol
+   across bridges using systemd-networkd.
+
+   Test topology:
+
+              1       2       3
+       node1 --- sw1 --- sw2 --- node2
+                   \     /
+                  4 \   / 5
+                     sw3
+                      |
+                    6 |
+                      |
+                    node3
+
+   where switches 1, 2, and 3 bridge their links and use STP,
+   and each link is labeled with the VLAN we are assigning it in
+   virtualisation.vlans.
+*/
+with builtins;
+let
+  commonConf = {
+    systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+    networking.useNetworkd = true;
+    networking.useDHCP = false;
+    networking.firewall.enable = false;
+  };
+
+  generateNodeConf = { octet, vlan }:
+    { lib, pkgs, config, ... }: {
+      imports = [ common/user-account.nix commonConf ];
+      virtualisation.vlans = [ vlan ];
+      systemd.network = {
+        enable = true;
+        networks = {
+          "30-eth" = {
+            matchConfig.Name = "eth1";
+            address = [ "10.0.0.${toString octet}/24" ];
+          };
+        };
+      };
+    };
+
+  generateSwitchConf = vlans:
+    { lib, pkgs, config, ... }: {
+      imports = [ common/user-account.nix commonConf ];
+      virtualisation.vlans = vlans;
+      systemd.network = {
+        enable = true;
+        netdevs = {
+          "40-br0" = {
+            netdevConfig = {
+              Kind = "bridge";
+              Name = "br0";
+            };
+            bridgeConfig.STP = "yes";
+          };
+        };
+        networks = {
+          "30-eth" = {
+            matchConfig.Name = "eth*";
+            networkConfig.Bridge = "br0";
+          };
+          "40-br0" = { matchConfig.Name = "br0"; };
+        };
+      };
+    };
+in import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "networkd";
+  meta = with pkgs.lib.maintainers; { maintainers = [ picnoir ]; };
+  nodes = {
+    node1 = generateNodeConf {
+      octet = 1;
+      vlan = 1;
+    };
+    node2 = generateNodeConf {
+      octet = 2;
+      vlan = 3;
+    };
+    node3 = generateNodeConf {
+      octet = 3;
+      vlan = 6;
+    };
+    sw1 = generateSwitchConf [ 1 2 4 ];
+    sw2 = generateSwitchConf [ 2 3 5 ];
+    sw3 = generateSwitchConf [ 4 5 6 ];
+  };
+  testScript = ''
+    network_nodes = [node1, node2, node3]
+    network_switches = [sw1, sw2, sw3]
+    start_all()
+
+    for n in network_nodes + network_switches:
+        n.wait_for_unit("systemd-networkd-wait-online.service")
+
+    node1.succeed("ping 10.0.0.2 -w 10 -c 1")
+    node1.succeed("ping 10.0.0.3 -w 10 -c 1")
+    node2.succeed("ping 10.0.0.1 -w 10 -c 1")
+    node2.succeed("ping 10.0.0.3 -w 10 -c 1")
+    node3.succeed("ping 10.0.0.1 -w 10 -c 1")
+    node3.succeed("ping 10.0.0.2 -w 10 -c 1")
+  '';
+})
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 1a39cc73c8868..4b087d403f37d 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "systemd";
 
-  nodes.machine = { lib, ... }: {
+  nodes.machine = { config, lib, ... }: {
     imports = [ common/user-account.nix common/x11.nix ];
 
     virtualisation.emptyDiskImages = [ 512 512 ];
@@ -38,9 +38,18 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       script = "true";
     };
 
+    systemd.services.testDependency1 = {
+      description = "Test Dependency 1";
+      wantedBy = [ config.systemd.services."testservice1".name ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        true
+      '';
+    };
+
     systemd.services.testservice1 = {
       description = "Test Service 1";
-      wantedBy = [ "multi-user.target" ];
+      wantedBy = [ config.systemd.targets.multi-user.name ];
       serviceConfig.Type = "oneshot";
       script = ''
         if [ "$XXX_SYSTEM" = foo ]; then
diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix
index d68917c6c7acb..2fb347155759a 100644
--- a/nixos/tests/teleport.nix
+++ b/nixos/tests/teleport.nix
@@ -9,8 +9,8 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 let
   packages = with pkgs; {
     "default" = teleport;
-    "12" = teleport_12;
     "13" = teleport_13;
+    "14" = teleport_14;
   };
 
   minimal = package: {
diff --git a/nixos/tests/web-apps/gotosocial.nix b/nixos/tests/web-apps/gotosocial.nix
index 8c4e76b14e3bf..f9d28c2b8b998 100644
--- a/nixos/tests/web-apps/gotosocial.nix
+++ b/nixos/tests/web-apps/gotosocial.nix
@@ -1,7 +1,7 @@
 { lib, ... }:
 {
   name = "gotosocial";
-  meta.maintainers = with lib.maintainers; [ misuzu blakesmith ];
+  meta.maintainers = with lib.maintainers; [ blakesmith ];
 
   nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.jq ];
diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix
index 8c701ca7d5f71..5e3b39f27ecf3 100644
--- a/nixos/tests/wpa_supplicant.nix
+++ b/nixos/tests/wpa_supplicant.nix
@@ -102,17 +102,34 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
           test2.psk = "@PSK_SPECIAL@";            # should be replaced
           test3.psk = "@PSK_MISSING@";            # should not be replaced
           test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced
+          test5.psk = "@PSK_AWK_REGEX@";          # should be replaced
         };
 
         # secrets
         environmentFile = pkgs.writeText "wpa-secrets" ''
           PSK_VALID="S0m3BadP4ssw0rd";
           # taken from https://github.com/minimaxir/big-list-of-naughty-strings
-          PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~";
+          PSK_SPECIAL=",./;'[]\/\-= <>?:\"{}|_+ !@#$%^&*()`~";
+          PSK_AWK_REGEX="PassowrdWith&symbol";
         '';
       };
     };
 
+    imperative = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ];
+
+      # add a virtual wlan interface
+      boot.kernelModules = [ "mac80211_hwsim" ];
+
+      # wireless client
+      networking.wireless = {
+        enable = lib.mkOverride 0 true;
+        userControlled.enable = true;
+        allowAuxiliaryImperativeNetworks = true;
+        interfaces = [ "wlan1" ];
+      };
+    };
+
     # Test connecting to the SAE-only hotspot using SAE
     machineSae = machineWithHostapd {
       networking.wireless = {
@@ -171,6 +188,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
           basic.fail(f"grep -q @PSK_SPECIAL@ {config_file}")
           basic.succeed(f"grep -q @PSK_MISSING@ {config_file}")
           basic.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}")
+          basic.succeed(f"grep -q 'PassowrdWith&symbol' {config_file}")
 
       with subtest("WPA2 fallbacks have been generated"):
           assert int(basic.succeed(f"grep -c sae-only {config_file}")) == 1
@@ -185,6 +203,15 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
           assert "Failed to connect" not in status, \
                  "Failed to connect to the daemon"
 
+      with subtest("Daemon can be configured imperatively"):
+          imperative.wait_for_unit("wpa_supplicant-wlan1.service")
+          imperative.wait_until_succeeds("wpa_cli -i wlan1 status")
+          imperative.succeed("wpa_cli -i wlan1 add_network")
+          imperative.succeed("wpa_cli -i wlan1 set_network 0 ssid '\"nixos-test\"'")
+          imperative.succeed("wpa_cli -i wlan1 set_network 0 psk '\"reproducibility\"'")
+          imperative.succeed("wpa_cli -i wlan1 save_config")
+          imperative.succeed("grep -q nixos-test /etc/wpa_supplicant.conf")
+
       machineSae.wait_for_unit("hostapd.service")
       machineSae.copy_from_vm("/run/hostapd/wlan0.hostapd.conf")
       with subtest("Daemon can connect to the SAE access point using SAE"):