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.nix11
-rw-r--r--nixos/tests/artalk.nix28
-rw-r--r--nixos/tests/ayatana-indicators.nix7
-rw-r--r--nixos/tests/benchexec.nix54
-rw-r--r--nixos/tests/castopod.nix3
-rw-r--r--nixos/tests/commafeed.nix21
-rw-r--r--nixos/tests/filesender.nix137
-rw-r--r--nixos/tests/firefly-iii.nix97
-rw-r--r--nixos/tests/garage/default.nix1
-rw-r--r--nixos/tests/garage/with-3node-replication.nix8
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix2
-rw-r--r--nixos/tests/installer.nix14
-rw-r--r--nixos/tests/keepalived.nix4
-rw-r--r--nixos/tests/kernel-generic.nix2
-rw-r--r--nixos/tests/lomiri.nix143
-rw-r--r--nixos/tests/mediamtx.nix95
-rw-r--r--nixos/tests/mysql/common.nix2
-rw-r--r--nixos/tests/mysql/mysql.nix2
-rw-r--r--nixos/tests/stalwart-mail.nix20
-rw-r--r--nixos/tests/step-ca.nix23
-rw-r--r--nixos/tests/stub-ld.nix4
-rw-r--r--nixos/tests/systemd-initrd-luks-fido2.nix1
-rw-r--r--nixos/tests/systemd-resolved.nix75
-rw-r--r--nixos/tests/tigervnc.nix8
-rw-r--r--nixos/tests/vector.nix37
-rw-r--r--nixos/tests/vector/api.nix39
-rw-r--r--nixos/tests/vector/default.nix11
-rw-r--r--nixos/tests/vector/dnstap.nix118
-rw-r--r--nixos/tests/vector/file-sink.nix49
-rw-r--r--nixos/tests/vector/nginx-clickhouse.nix168
-rw-r--r--nixos/tests/virtualbox.nix1
-rw-r--r--nixos/tests/web-apps/nextjs-ollama-llm-ui.nix22
-rw-r--r--nixos/tests/web-apps/pretalx.nix5
-rw-r--r--nixos/tests/your_spotify.nix33
34 files changed, 1070 insertions, 175 deletions
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 3f3a99a83fee0..035c288c22e5c 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -130,6 +130,7 @@ in {
   apparmor = handleTest ./apparmor.nix {};
   archi = handleTest ./archi.nix {};
   armagetronad = handleTest ./armagetronad.nix {};
+  artalk = handleTest ./artalk.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
@@ -144,6 +145,7 @@ in {
   bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
+  benchexec = handleTest ./benchexec.nix {};
   binary-cache = handleTest ./binary-cache.nix {};
   bind = handleTest ./bind.nix {};
   bird = handleTest ./bird.nix {};
@@ -204,6 +206,7 @@ in {
   code-server = handleTest ./code-server.nix {};
   coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
+  commafeed = handleTest ./commafeed.nix {};
   connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
   consul-template = handleTest ./consul-template.nix {};
@@ -309,6 +312,7 @@ in {
   fenics = handleTest ./fenics.nix {};
   ferm = handleTest ./ferm.nix {};
   ferretdb = handleTest ./ferretdb.nix {};
+  filesender = handleTest ./filesender.nix {};
   filesystems-overlayfs = runTest ./filesystems-overlayfs.nix;
   firefly-iii = handleTest ./firefly-iii.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
@@ -587,7 +591,7 @@ in {
   mysql-backup = handleTest ./mysql/mysql-backup.nix {};
   mysql-replication = handleTest ./mysql/mysql-replication.nix {};
   n8n = handleTest ./n8n.nix {};
-  nagios = handleTest ./nagios.nix {};
+  nagios = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nagios.nix {};
   nar-serve = handleTest ./nar-serve.nix {};
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
@@ -612,6 +616,7 @@ in {
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
+  nextjs-ollama-llm-ui = runTest ./web-apps/nextjs-ollama-llm-ui.nix;
   nexus = handleTest ./nexus.nix {};
   # TODO: Test nfsv3 + Kerberos
   nfs3 = handleTest ./nfs { version = 3; };
@@ -926,6 +931,7 @@ in {
   systemd-oomd = handleTest ./systemd-oomd.nix {};
   systemd-portabled = handleTest ./systemd-portabled.nix {};
   systemd-repart = handleTest ./systemd-repart.nix {};
+  systemd-resolved = handleTest ./systemd-resolved.nix {};
   systemd-shutdown = handleTest ./systemd-shutdown.nix {};
   systemd-sysupdate = runTest ./systemd-sysupdate.nix;
   systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
@@ -1004,7 +1010,7 @@ in {
   vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
-  vector = handleTest ./vector.nix {};
+  vector = handleTest ./vector {};
   vengi-tools = handleTest ./vengi-tools.nix {};
   victoriametrics = handleTest ./victoriametrics.nix {};
   vikunja = handleTest ./vikunja.nix {};
@@ -1042,6 +1048,7 @@ in {
   yabar = handleTest ./yabar.nix {};
   ydotool = handleTest ./ydotool.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
+  your_spotify = handleTest ./your_spotify.nix {};
   zammad = handleTest ./zammad.nix {};
   zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
   zfs = handleTest ./zfs.nix {};
diff --git a/nixos/tests/artalk.nix b/nixos/tests/artalk.nix
new file mode 100644
index 0000000000000..1338e5cd380c6
--- /dev/null
+++ b/nixos/tests/artalk.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix (
+  { lib, pkgs, ... }:
+  {
+
+    name = "artalk";
+
+    meta = {
+      maintainers = with lib.maintainers; [ moraxyc ];
+    };
+
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ pkgs.curl ];
+        services.artalk = {
+          enable = true;
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("artalk.service")
+
+      machine.wait_for_open_port(23366)
+
+      machine.succeed("curl --fail --max-time 10 http://127.0.0.1:23366/")
+    '';
+  }
+)
diff --git a/nixos/tests/ayatana-indicators.nix b/nixos/tests/ayatana-indicators.nix
index 5709ad2a1af69..cfd4d8099d112 100644
--- a/nixos/tests/ayatana-indicators.nix
+++ b/nixos/tests/ayatana-indicators.nix
@@ -28,8 +28,11 @@ in {
       enable = true;
       packages = with pkgs; [
         ayatana-indicator-datetime
+        ayatana-indicator-display
         ayatana-indicator-messages
+        ayatana-indicator-power
         ayatana-indicator-session
+        ayatana-indicator-sound
       ] ++ (with pkgs.lomiri; [
         lomiri-indicator-network
         telephony-service
@@ -40,6 +43,8 @@ in {
 
     services.accounts-daemon.enable = true; # messages
 
+    hardware.pulseaudio.enable = true; # sound
+
     # Lomiri-ish setup for Lomiri indicators
     # TODO move into a Lomiri module, once the package set is far enough for the DE to start
 
@@ -91,7 +96,7 @@ in {
 
     # Now check if all indicators were brought up successfully, and kill them for later
   '' + (runCommandOverAyatanaIndicators (service: let serviceExec = builtins.replaceStrings [ "." ] [ "-" ] service; in ''
-    machine.succeed("pgrep -u ${user} -f ${serviceExec}")
+    machine.wait_until_succeeds("pgrep -u ${user} -f ${serviceExec}")
     machine.succeed("pkill -f ${serviceExec}")
   '')) + ''
 
diff --git a/nixos/tests/benchexec.nix b/nixos/tests/benchexec.nix
new file mode 100644
index 0000000000000..3fc9ebc2c35f5
--- /dev/null
+++ b/nixos/tests/benchexec.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  user = "alice";
+in
+{
+  name = "benchexec";
+
+  nodes.benchexec = {
+    imports = [ ./common/user-account.nix ];
+
+    programs.benchexec = {
+      enable = true;
+      users = [ user ];
+    };
+  };
+
+  testScript = { ... }:
+    let
+      runexec = lib.getExe' pkgs.benchexec "runexec";
+      echo = builtins.toString pkgs.benchexec;
+      test = lib.getExe (pkgs.writeShellApplication rec {
+        name = "test";
+        meta.mainProgram = name;
+        text = "echo '${echo}'";
+      });
+      wd = "/tmp";
+      stdout = "${wd}/runexec.out";
+      stderr = "${wd}/runexec.err";
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      benchexec.succeed(''''\
+          systemd-run \
+            --property='StandardOutput=file:${stdout}' \
+            --property='StandardError=file:${stderr}' \
+            --unit=runexec --wait --user --machine='${user}@' \
+            --working-directory ${wd} \
+          '${runexec}' \
+            --debug \
+            --read-only-dir / \
+            --hidden-dir /home \
+            '${test}' \
+      '''')
+      benchexec.succeed("grep -s '${echo}' ${wd}/output.log")
+      benchexec.succeed("test \"$(grep -Ec '((start|wall|cpu)time|memory)=' ${stdout})\" = 4")
+      benchexec.succeed("! grep -E '(WARNING|ERROR)' ${stderr}")
+    '';
+
+  interactive.nodes.benchexec.services.kmscon = {
+    enable = true;
+    fonts = [{ name = "Fira Code"; package = pkgs.fira-code; }];
+  };
+})
diff --git a/nixos/tests/castopod.nix b/nixos/tests/castopod.nix
index 3257cd3d363c7..57e035354d23e 100644
--- a/nixos/tests/castopod.nix
+++ b/nixos/tests/castopod.nix
@@ -98,6 +98,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             driver = Firefox(options=options, service=service)
             driver = Firefox(options=options)
             driver.implicitly_wait(30)
+            driver.set_page_load_timeout(60)
 
             # install ##########################################################
 
@@ -207,7 +208,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             text = ''
               out=/tmp/podcast.mp3
               sox -n -r 48000 -t wav - synth ${targetPodcastDuration} sine 440 `
-              `| lame --noreplaygain -cbr -q 9 -b 320 - $out
+              `| lame --noreplaygain --cbr -q 9 -b 320 - $out
               FILESIZE="$(stat -c%s $out)"
               [ "$FILESIZE" -gt 0 ]
               [ "$FILESIZE" -le "${toString targetPodcastSize}" ]
diff --git a/nixos/tests/commafeed.nix b/nixos/tests/commafeed.nix
new file mode 100644
index 0000000000000..7b65720818a9b
--- /dev/null
+++ b/nixos/tests/commafeed.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix (
+  { lib, ... }:
+  {
+    name = "commafeed";
+
+    nodes.server = {
+      services.commafeed = {
+        enable = true;
+      };
+    };
+
+    testScript = ''
+      server.start()
+      server.wait_for_unit("commafeed.service")
+      server.wait_for_open_port(8082)
+      server.succeed("curl --fail --silent http://localhost:8082")
+    '';
+
+    meta.maintainers = [ lib.maintainers.raroh73 ];
+  }
+)
diff --git a/nixos/tests/filesender.nix b/nixos/tests/filesender.nix
new file mode 100644
index 0000000000000..9274ddbf7e90e
--- /dev/null
+++ b/nixos/tests/filesender.nix
@@ -0,0 +1,137 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "filesender";
+  meta = {
+    maintainers = with lib.maintainers; [ nhnn ];
+    broken = pkgs.stdenv.isAarch64; # selenium.common.exceptions.WebDriverException: Message: Unsupported platform/architecture combination: linux/aarch64
+  };
+
+  nodes.filesender = { ... }: let
+    format = pkgs.formats.php { };
+  in {
+    networking.firewall.allowedTCPPorts = [ 80 ];
+
+    services.filesender.enable = true;
+    services.filesender.localDomain = "filesender";
+    services.filesender.settings = {
+      auth_sp_saml_authentication_source = "default";
+      auth_sp_saml_uid_attribute = "uid";
+      storage_filesystem_path = "/tmp";
+      site_url = "http://filesender";
+      force_ssl = false;
+      admin = "";
+      admin_email = "admin@localhost";
+      email_reply_to = "noreply@localhost";
+    };
+    services.simplesamlphp.filesender = {
+      settings = {
+        baseurlpath = "http://filesender/saml";
+        "module.enable".exampleauth = true;
+      };
+      authSources = {
+        admin = [ "core:AdminPassword" ];
+        default = format.lib.mkMixedArray [ "exampleauth:UserPass" ] {
+          "user:password" = {
+            uid = [ "user" ];
+            cn = [ "user" ];
+            mail = [ "user@nixos.org" ];
+          };
+        };
+      };
+    };
+  };
+
+  nodes.client =
+    { pkgs
+    , nodes
+    , ...
+    }:
+    let
+      filesenderIP = (builtins.head (nodes.filesender.networking.interfaces.eth1.ipv4.addresses)).address;
+    in
+    {
+      networking.hosts.${filesenderIP} = [ "filesender" ];
+
+      environment.systemPackages =
+        let
+          username = "user";
+          password = "password";
+          browser-test =
+            pkgs.writers.writePython3Bin "browser-test"
+              {
+                libraries = [ pkgs.python3Packages.selenium ];
+                flakeIgnore = [ "E124" "E501" ];
+              } ''
+              from selenium.webdriver.common.by import By
+              from selenium.webdriver import Firefox
+              from selenium.webdriver.firefox.options import Options
+              from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
+              from selenium.webdriver.firefox.service import Service
+              from selenium.webdriver.support.ui import WebDriverWait
+              from selenium.webdriver.support import expected_conditions as EC
+              from subprocess import STDOUT
+              import string
+              import random
+              import logging
+              import time
+              selenium_logger = logging.getLogger("selenium")
+              selenium_logger.setLevel(logging.DEBUG)
+              selenium_logger.addHandler(logging.StreamHandler())
+              profile = FirefoxProfile()
+              profile.set_preference("browser.download.folderList", 2)
+              profile.set_preference("browser.download.manager.showWhenStarting", False)
+              profile.set_preference("browser.download.dir", "/tmp/firefox")
+              profile.set_preference("browser.helperApps.neverAsk.saveToDisk", "text/plain;text/txt")
+              options = Options()
+              options.profile = profile
+              options.add_argument('--headless')
+              service = Service(log_output=STDOUT)
+              driver = Firefox(options=options)
+              driver.set_window_size(1024, 768)
+              driver.implicitly_wait(30)
+              driver.get('http://filesender/')
+              wait = WebDriverWait(driver, 20)
+              wait.until(EC.title_contains("FileSender"))
+              driver.find_element(By.ID, "btn_logon").click()
+              wait.until(EC.title_contains("Enter your username and password"))
+              driver.find_element(By.ID, 'username').send_keys(
+                  '${username}'
+              )
+              driver.find_element(By.ID, 'password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.ID, "submit_button").click()
+              wait.until(EC.title_contains("FileSender"))
+              wait.until(EC.presence_of_element_located((By.ID, "topmenu_logoff")))
+              test_string = "".join(random.choices(string.ascii_uppercase + string.digits, k=20))
+              with open("/tmp/test_file.txt", "w") as file:
+                  file.write(test_string)
+              driver.find_element(By.ID, "files").send_keys("/tmp/test_file.txt")
+              time.sleep(2)
+              driver.find_element(By.CSS_SELECTOR, '.start').click()
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".download_link")))
+              download_link = driver.find_element(By.CSS_SELECTOR, '.download_link > textarea').get_attribute('value').strip()
+              driver.get(download_link)
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".download")))
+              driver.find_element(By.CSS_SELECTOR, '.download').click()
+              wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".ui-dialog-buttonset > button:nth-child(2)")))
+              driver.find_element(By.CSS_SELECTOR, ".ui-dialog-buttonset > button:nth-child(2)").click()
+              driver.close()
+              driver.quit()
+            '';
+        in
+        [
+          pkgs.firefox-unwrapped
+          pkgs.geckodriver
+          browser-test
+        ];
+    };
+
+  testScript = ''
+    start_all()
+    filesender.wait_for_file("/run/phpfpm/filesender.sock")
+    filesender.wait_for_open_port(80)
+    if "If you have received an invitation to access this site as a guest" not in client.wait_until_succeeds("curl -sS -f http://filesender"):
+      raise Exception("filesender returned invalid html")
+    client.succeed("browser-test")
+  '';
+})
diff --git a/nixos/tests/firefly-iii.nix b/nixos/tests/firefly-iii.nix
index c93d799320a48..2373ba8360264 100644
--- a/nixos/tests/firefly-iii.nix
+++ b/nixos/tests/firefly-iii.nix
@@ -1,14 +1,19 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }: {
+import ./make-test-python.nix ({ lib, ... }:
+
+let
+  db-pass = "Test2Test2";
+  app-key = "TestTestTestTestTestTestTestTest";
+in
+{
   name = "firefly-iii";
   meta.maintainers = [ lib.maintainers.savyajha ];
 
-  nodes.machine = { config, ... }: {
+  nodes.fireflySqlite = { config, ... }: {
     environment.etc = {
-      "firefly-iii-appkey".text = "TestTestTestTestTestTestTestTest";
+      "firefly-iii-appkey".text = app-key;
     };
     services.firefly-iii = {
       enable = true;
-      virtualHost = "http://localhost";
       enableNginx = true;
       settings = {
         APP_KEY_FILE = "/etc/firefly-iii-appkey";
@@ -18,9 +23,87 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     };
   };
 
+  nodes.fireflyPostgresql = { config, pkgs, ... }: {
+    environment.etc = {
+      "firefly-iii-appkey".text = app-key;
+      "postgres-pass".text = db-pass;
+    };
+    services.firefly-iii = {
+      enable = true;
+      enableNginx = true;
+      settings = {
+        APP_KEY_FILE = "/etc/firefly-iii-appkey";
+        LOG_CHANNEL = "stdout";
+        SITE_OWNER = "mail@example.com";
+        DB_CONNECTION = "pgsql";
+        DB_DATABASE = "firefly";
+        DB_USERNAME = "firefly";
+        DB_PASSWORD_FILE = "/etc/postgres-pass";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      package = pkgs.postgresql_15;
+      authentication = ''
+        local all postgres peer
+        local firefly firefly password
+      '';
+      initialScript = pkgs.writeText "firefly-init.sql" ''
+        CREATE USER "firefly" WITH LOGIN PASSWORD '${db-pass}';
+        CREATE DATABASE "firefly" WITH OWNER "firefly";
+        CREATE SCHEMA AUTHORIZATION firefly;
+      '';
+    };
+  };
+
+  nodes.fireflyMysql = { config, pkgs, ... }: {
+    environment.etc = {
+      "firefly-iii-appkey".text = app-key;
+      "mysql-pass".text = db-pass;
+    };
+    services.firefly-iii = {
+      enable = true;
+      enableNginx = true;
+      settings = {
+        APP_KEY_FILE = "/etc/firefly-iii-appkey";
+        LOG_CHANNEL = "stdout";
+        SITE_OWNER = "mail@example.com";
+        DB_CONNECTION = "mysql";
+        DB_DATABASE = "firefly";
+        DB_USERNAME = "firefly";
+        DB_PASSWORD_FILE = "/etc/mysql-pass";
+        DB_SOCKET = "/run/mysqld/mysqld.sock";
+      };
+    };
+
+    services.mysql = {
+      enable = true;
+      package = pkgs.mariadb;
+      initialScript = pkgs.writeText "firefly-init.sql" ''
+        create database firefly DEFAULT CHARACTER SET utf8mb4;
+        create user 'firefly'@'localhost' identified by '${db-pass}';
+        grant all on firefly.* to 'firefly'@'localhost';
+      '';
+      settings.mysqld.character-set-server = "utf8mb4";
+    };
+  };
+
   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'")
+    fireflySqlite.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflySqlite.wait_for_unit("nginx.service")
+    fireflySqlite.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflySqlite.succeed("curl -fvvv -Ls http://localhost/v1/js/app.js")
+    fireflySqlite.succeed("systemctl start firefly-iii-cron.service")
+    fireflyPostgresql.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflyPostgresql.wait_for_unit("nginx.service")
+    fireflyPostgresql.wait_for_unit("postgresql.service")
+    fireflyPostgresql.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflyPostgresql.succeed("systemctl start firefly-iii-cron.service")
+    fireflyMysql.wait_for_unit("phpfpm-firefly-iii.service")
+    fireflyMysql.wait_for_unit("nginx.service")
+    fireflyMysql.wait_for_unit("mysql.service")
+    fireflyMysql.succeed("curl -fvvv -Ls http://localhost/ | grep 'Firefly III'")
+    fireflyMysql.succeed("systemctl start firefly-iii-cron.service")
   '';
 })
diff --git a/nixos/tests/garage/default.nix b/nixos/tests/garage/default.nix
index a42236e9a5bbe..b7f9bb4b865bd 100644
--- a/nixos/tests/garage/default.nix
+++ b/nixos/tests/garage/default.nix
@@ -51,4 +51,5 @@ in
   [
     "0_8"
     "0_9"
+    "1_x"
   ]
diff --git a/nixos/tests/garage/with-3node-replication.nix b/nixos/tests/garage/with-3node-replication.nix
index d4387b198d976..266a1082893f7 100644
--- a/nixos/tests/garage/with-3node-replication.nix
+++ b/nixos/tests/garage/with-3node-replication.nix
@@ -7,10 +7,10 @@ args@{ mkNode, ver, ... }:
   };
 
   nodes = {
-    node1 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::1"; };
-    node2 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::2"; };
-    node3 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::3"; };
-    node4 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::4"; };
+    node1 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::1"; };
+    node2 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::2"; };
+    node3 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::3"; };
+    node4 = mkNode { replicationMode = "3"; publicV6Address = "fc00:1::4"; };
   };
 
   testScript = ''
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
index 1dd55dada042a..00205f9417718 100644
--- a/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -19,7 +19,7 @@
     luksroot
     luksroot-format1
     luksroot-format2
-    # lvm
+    lvm
     separateBoot
     separateBootFat
     separateBootZfs
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 7e835041eb39f..3f57a64333dda 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -249,12 +249,11 @@ let
       with subtest("Check whether nixos-rebuild works"):
           target.succeed("nixos-rebuild switch >&2")
 
-      # FIXME: Nix 2.4 broke nixos-option, someone has to fix it.
-      # with subtest("Test nixos-option"):
-      #     kernel_modules = target.succeed("nixos-option boot.initrd.kernelModules")
-      #     assert "virtio_console" in kernel_modules
-      #     assert "List of modules" in kernel_modules
-      #     assert "qemu-guest.nix" in kernel_modules
+      with subtest("Test nixos-option"):
+          kernel_modules = target.succeed("nixos-option boot.initrd.kernelModules")
+          assert "virtio_console" in kernel_modules
+          assert "List of modules" in kernel_modules
+          assert "qemu-guest.nix" in kernel_modules
 
       target.shutdown()
 
@@ -975,6 +974,9 @@ in {
           "mount LABEL=nixos /mnt",
       )
     '';
+    extraConfig = optionalString systemdStage1 ''
+      boot.initrd.services.lvm.enable = true;
+    '';
   };
 
   # Boot off an encrypted root partition with the default LUKS header format
diff --git a/nixos/tests/keepalived.nix b/nixos/tests/keepalived.nix
index 16564511d85dc..052b36266d037 100644
--- a/nixos/tests/keepalived.nix
+++ b/nixos/tests/keepalived.nix
@@ -4,8 +4,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   nodes = {
     node1 = { pkgs, ... }: {
-      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
       services.keepalived.enable = true;
+      services.keepalived.openFirewall = true;
       services.keepalived.vrrpInstances.test = {
         interface = "eth1";
         state = "MASTER";
@@ -16,8 +16,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       environment.systemPackages = [ pkgs.tcpdump ];
     };
     node2 = { pkgs, ... }: {
-      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
       services.keepalived.enable = true;
+      services.keepalived.openFirewall = true;
       services.keepalived.vrrpInstances.test = {
         interface = "eth1";
         state = "MASTER";
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 5f0e7b3e37cd7..11a3bf80c15e4 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -31,6 +31,8 @@ let
       linux_5_15_hardened
       linux_6_1_hardened
       linux_6_6_hardened
+      linux_6_8_hardened
+      linux_6_9_hardened
       linux_rt_5_4
       linux_rt_5_10
       linux_rt_5_15
diff --git a/nixos/tests/lomiri.nix b/nixos/tests/lomiri.nix
index 9d6337e9977cb..99f04a303be31 100644
--- a/nixos/tests/lomiri.nix
+++ b/nixos/tests/lomiri.nix
@@ -19,9 +19,13 @@ in {
       inherit description password;
     };
 
+    # To control mouse via scripting
+    programs.ydotool.enable = true;
+
     services.desktopManager.lomiri.enable = lib.mkForce true;
     services.displayManager.defaultSession = lib.mkForce "lomiri";
 
+    # Help with OCR
     fonts.packages = [ pkgs.inconsolata ];
 
     environment = {
@@ -114,17 +118,9 @@ in {
   enableOCR = true;
 
   testScript = { nodes, ... }: ''
-    def open_starter():
-        """
-        Open the starter, and ensure it's opened.
-        """
-        machine.send_key("meta_l-a")
-        # Look for any of the default apps
-        machine.wait_for_text(r"(Search|System|Settings|Morph|Browser|Terminal|Alacritty)")
-
     def toggle_maximise():
         """
-        Send the keybind to maximise the current window.
+        Maximise the current window.
         """
         machine.send_key("ctrl-meta_l-up")
 
@@ -135,13 +131,43 @@ in {
         machine.send_key("esc")
         machine.sleep(5)
 
+    def mouse_click(xpos, ypos):
+        """
+        Move the mouse to a screen location and hit left-click.
+        """
+
+        # Need to reset to top-left, --absolute doesn't work?
+        machine.execute("ydotool mousemove -- -10000 -10000")
+        machine.sleep(2)
+
+        # Move
+        machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
+        machine.sleep(2)
+
+        # Click (C0 - left button: down & up)
+        machine.execute("ydotool click 0xC0")
+        machine.sleep(2)
+
+    def open_starter():
+        """
+        Open the starter, and ensure it's opened.
+        """
+
+        # Using the keybind has a chance of instantly closing the menu again? Just click the button
+        mouse_click(20, 30)
+
+        # Look for Search box & GUI-less content-hub examples, highest chances of avoiding false positives
+        machine.wait_for_text(r"(Search|Export|Import|Share)")
+
     start_all()
     machine.wait_for_unit("multi-user.target")
 
     # Lomiri in greeter mode should work & be able to start a session
     with subtest("lomiri greeter works"):
         machine.wait_for_unit("display-manager.service")
-        # Start page shows current tie
+        machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
+
+        # Start page shows current time
         machine.wait_for_text(r"(AM|PM)")
         machine.screenshot("lomiri_greeter_launched")
 
@@ -152,7 +178,6 @@ in {
 
         # Login
         machine.send_chars("${password}\n")
-        # Best way I can think of to differenciate "Lomiri in LightDM greeter mode" from "Lomiri in user shell mode"
         machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
 
     # The session should start, and not be stuck in i.e. a crash loop
@@ -196,6 +221,17 @@ in {
         machine.screenshot("alacritty_opens")
         machine.send_key("alt-f4")
 
+    # Morph is how we go online
+    with subtest("morph browser works"):
+        open_starter()
+        machine.send_chars("Morph\n")
+        machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
+        machine.screenshot("morph_open")
+
+        # morph-browser has a separate VM test, there isn't anything new we could test here
+
+        # Keep it running, we're using it to check content-hub communication from LSS
+
     # LSS provides DE settings
     with subtest("system settings open"):
         open_starter()
@@ -231,64 +267,75 @@ in {
         machine.wait_for_text("Morph") # or Gallery, but Morph is already packaged
         machine.screenshot("settings_content-hub_peers")
 
-        # Sadly, it doesn't seem possible to actually select a peer and attempt a content-hub data exchange with just the keyboard
+        # Select Morph as content source
+        mouse_click(300, 100)
 
-        machine.send_key("alt-f4")
+        # Expect Morph to be brought into the foreground, with its Downloads page open
+        machine.wait_for_text("No downloads")
 
-    # Morph is how we go online
-    with subtest("morph browser works"):
-        open_starter()
-        machine.send_chars("Morph\n")
-        machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
-        machine.screenshot("morph_open")
+        # If content-hub encounters a problem, it may have crashed the original application issuing the request.
+        # Check that it's still alive
+        machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
 
-        # morph-browser has a separate VM test, there isn't anything new we could test here
+        machine.screenshot("content-hub_exchange")
 
-        machine.send_key("alt-f4")
+        # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
+        machine.send_key("esc")
+
+        machine.send_key("alt-f4") # LSS
+        machine.sleep(2) # focus is slow to switch to second window, closing it *really* helps with OCR afterwards
+        machine.send_key("alt-f4") # Morph
 
     # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
-    # Reaching them via the intended way requires wayland mouse control, but ydotool lacks a module for its daemon:
-    # https://github.com/NixOS/nixpkgs/issues/183659
-    # Luckily, there's a test app that also displays their contents, but it's abit inconsistent. Hopefully this is *good-enough*.
+    # There's a test app we could use that also displays their contents, but it's abit inconsistent.
     with subtest("ayatana indicators work"):
-        open_starter()
-        machine.send_chars("Indicators\n")
-        machine.wait_for_text(r"(Indicators|Client|List|network|datetime|session)")
+        mouse_click(735, 0) # the cog in the top-right, for the session indicator
+        machine.wait_for_text(r"(Notifications|Rotation|Battery|Sound|Time|Date|System)")
         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
+        # Indicator order within the menus *should* be fixed based on per-indicator order setting
+        # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        machine.send_key("left")
+        # Notifications are usually empty, nothing to check there
+
+        with subtest("ayatana indicator display works"):
+            # We start on this, don't go right
+            machine.wait_for_text("Lock")
+            machine.screenshot("indicators_display")
 
         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.send_key("right")
             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 sound works"):
+            machine.send_key("right")
+            machine.wait_for_text(r"(Silent|Volume)")
+            machine.screenshot("indicators_sound")
+
+        with subtest("ayatana indicator power works"):
+            machine.send_key("right")
+            machine.wait_for_text(r"(Charge|Battery settings)")
+            machine.screenshot("indicators_power")
 
         with subtest("ayatana indicator datetime works"):
-            # Select ayatana-indicator-datetime
-            machine.send_key("tab")
-            machine.send_key("down")
-            machine.send_key("ret")
+            machine.send_key("right")
             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|network|datetime|session)")
-
         with subtest("ayatana indicator session works"):
-            # Select ayatana-indicator-session
-            machine.send_key("tab")
-            machine.send_key("down")
-            machine.send_key("ret")
+            machine.send_key("right")
             machine.wait_for_text("Log Out")
             machine.screenshot("indicators_session")
+
+            # We should be able to log out and return to the greeter
+            mouse_click(720, 280) # "Log Out"
+            mouse_click(400, 240) # confirm logout
+            machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+            machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
   '';
 })
diff --git a/nixos/tests/mediamtx.nix b/nixos/tests/mediamtx.nix
index 8cacd02631d95..f40c4a8cb5832 100644
--- a/nixos/tests/mediamtx.nix
+++ b/nixos/tests/mediamtx.nix
@@ -1,57 +1,60 @@
-import ./make-test-python.nix ({ pkgs, lib, ...} :
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
 
-{
-  name = "mediamtx";
-  meta.maintainers = with lib.maintainers; [ fpletz ];
+  {
+    name = "mediamtx";
+    meta.maintainers = with lib.maintainers; [ fpletz ];
 
-  nodes = {
-    machine = { config, ... }: {
-      services.mediamtx = {
-        enable = true;
-        settings = {
-          metrics = true;
-          paths.all.source = "publisher";
+    nodes = {
+      machine = {
+        services.mediamtx = {
+          enable = true;
+          settings = {
+            metrics = true;
+            paths.all.source = "publisher";
+          };
         };
-      };
 
-      systemd.services.rtmp-publish = {
-        description = "Publish an RTMP stream to mediamtx";
-        after = [ "mediamtx.service" ];
-        bindsTo = [ "mediamtx.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          DynamicUser = true;
-          Restart = "on-failure";
-          RestartSec = "1s";
-          TimeoutStartSec = "10s";
-          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+        systemd.services.rtmp-publish = {
+          description = "Publish an RTMP stream to mediamtx";
+          after = [ "mediamtx.service" ];
+          bindsTo = [ "mediamtx.service" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            Restart = "on-failure";
+            RestartSec = "1s";
+            TimeoutStartSec = "10s";
+            ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+          };
         };
-      };
 
-      systemd.services.rtmp-receive = {
-        description = "Receive an RTMP stream from mediamtx";
-        after = [ "rtmp-publish.service" ];
-        bindsTo = [ "rtmp-publish.service" ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          DynamicUser = true;
-          Restart = "on-failure";
-          RestartSec = "1s";
-          TimeoutStartSec = "10s";
-          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+        systemd.services.rtmp-receive = {
+          description = "Receive an RTMP stream from mediamtx";
+          after = [ "rtmp-publish.service" ];
+          bindsTo = [ "rtmp-publish.service" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            Restart = "on-failure";
+            RestartSec = "1s";
+            TimeoutStartSec = "10s";
+            ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+          };
         };
       };
     };
-  };
 
-  testScript = ''
-    start_all()
+    testScript = ''
+      start_all()
 
-    machine.wait_for_unit("mediamtx.service")
-    machine.wait_for_unit("rtmp-publish.service")
-    machine.wait_for_unit("rtmp-receive.service")
-    machine.wait_for_open_port(9998)
-    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
-    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
-  '';
-})
+      machine.wait_for_unit("mediamtx.service")
+      machine.wait_for_unit("rtmp-publish.service")
+      machine.sleep(10)
+      machine.wait_for_unit("rtmp-receive.service")
+      machine.wait_for_open_port(9998)
+      machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
+      machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
+    '';
+  }
+)
diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix
index 1cf52347f4c74..ad54b0e00c1b3 100644
--- a/nixos/tests/mysql/common.nix
+++ b/nixos/tests/mysql/common.nix
@@ -4,7 +4,7 @@
     inherit (pkgs) mysql80;
   };
   perconaPackages = {
-    inherit (pkgs) percona-server_8_0;
+    inherit (pkgs) percona-server_lts percona-server_innovation;
   };
   mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}";
 }
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 0a61f9d38fe2e..093da4f46aa10 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -146,6 +146,6 @@ in
   }) mariadbPackages)
   // (lib.mapAttrs (_: package: makeMySQLTest {
     inherit package;
-    name = "percona_8_0";
+    name = builtins.replaceStrings ["-"] ["_"] package.pname;
     hasMroonga = false; useSocketAuth = false;
   }) perconaPackages)
diff --git a/nixos/tests/stalwart-mail.nix b/nixos/tests/stalwart-mail.nix
index 634c0e2e39261..173b4fce4ad5d 100644
--- a/nixos/tests/stalwart-mail.nix
+++ b/nixos/tests/stalwart-mail.nix
@@ -18,8 +18,8 @@ in import ./make-test-python.nix ({ lib, ... }: {
         server.hostname = domain;
 
         certificate."snakeoil" = {
-          cert = "file://${certs.${domain}.cert}";
-          private-key = "file://${certs.${domain}.key}";
+          cert = "%{file:${certs.${domain}.cert}}%";
+          private-key = "%{file:${certs.${domain}.key}}%";
         };
 
         server.tls = {
@@ -40,24 +40,24 @@ in import ./make-test-python.nix ({ lib, ... }: {
           };
         };
 
-        session.auth.mechanisms = [ "PLAIN" ];
-        session.auth.directory = "in-memory";
-        storage.directory = "in-memory";  # shared with imap
+        session.auth.mechanisms = "[plain]";
+        session.auth.directory = "'in-memory'";
+        storage.directory = "in-memory";
 
-        session.rcpt.directory = "in-memory";
-        queue.outbound.next-hop = [ "local" ];
+        session.rcpt.directory = "'in-memory'";
+        queue.outbound.next-hop = "'local'";
 
         directory."in-memory" = {
           type = "memory";
           principals = [
             {
-              type = "individual";
+              class = "individual";
               name = "alice";
               secret = "foobar";
               email = [ "alice@${domain}" ];
             }
             {
-              type = "individual";
+              class = "individual";
               name = "bob";
               secret = "foobar";
               email = [ "bob@${domain}" ];
@@ -115,6 +115,6 @@ in import ./make-test-python.nix ({ lib, ... }: {
   '';
 
   meta = {
-    maintainers = with lib.maintainers; [ happysalada pacien ];
+    maintainers = with lib.maintainers; [ happysalada pacien onny ];
   };
 })
diff --git a/nixos/tests/step-ca.nix b/nixos/tests/step-ca.nix
index a855b590232dd..184c35f6b85cc 100644
--- a/nixos/tests/step-ca.nix
+++ b/nixos/tests/step-ca.nix
@@ -62,6 +62,24 @@ import ./make-test-python.nix ({ pkgs, ... }:
             };
           };
 
+        caclientcaddy =
+          { config, pkgs, ... }: {
+            security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+
+            networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+            services.caddy = {
+              enable = true;
+              virtualHosts."caclientcaddy".extraConfig = ''
+                respond "Welcome to Caddy!"
+
+                tls caddy@example.org {
+                  ca https://caserver:8443/acme/acme/directory
+                }
+              '';
+            };
+          };
+
         catester = { config, pkgs, ... }: {
           security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
         };
@@ -71,7 +89,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
       ''
         catester.start()
         caserver.wait_for_unit("step-ca.service")
+        caserver.wait_until_succeeds("journalctl -o cat -u step-ca.service | grep '${pkgs.step-ca.version}'")
+
         caclient.wait_for_unit("acme-finished-caclient.target")
         catester.succeed("curl https://caclient/ | grep \"Welcome to nginx!\"")
+
+        caclientcaddy.wait_for_unit("caddy.service")
+        catester.succeed("curl https://caclientcaddy/ | grep \"Welcome to Caddy!\"")
       '';
   })
diff --git a/nixos/tests/stub-ld.nix b/nixos/tests/stub-ld.nix
index 25161301741b7..72b0aebf3e6ce 100644
--- a/nixos/tests/stub-ld.nix
+++ b/nixos/tests/stub-ld.nix
@@ -45,10 +45,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
           ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
 
       with subtest("Try FHS executable"):
-          machine.copy_from_host('${test-exec.${pkgs.system}}','test-exec')
+          machine.copy_from_host('${test-exec.${pkgs.stdenv.hostPlatform.system}}','test-exec')
           machine.succeed('if test-exec/${exec-name} 2>outfile; then false; else [ $? -eq 127 ];fi')
           machine.succeed('grep -qi nixos outfile')
-          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.system}}','test-exec32')"}
+          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.stdenv.hostPlatform.system}}','test-exec32')"}
           ${if32 "machine.succeed('if test-exec32/${exec-name} 2>outfile32; then false; else [ $? -eq 127 ];fi')"}
           ${if32 "machine.succeed('grep -qi nixos outfile32')"}
 
diff --git a/nixos/tests/systemd-initrd-luks-fido2.nix b/nixos/tests/systemd-initrd-luks-fido2.nix
index f9f75ab7f301c..207f51f4dd9b4 100644
--- a/nixos/tests/systemd-initrd-luks-fido2.nix
+++ b/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -9,7 +9,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       # Booting off the encrypted disk requires having a Nix store available for the init script
       mountHostNixStore = true;
       useEFIBoot = true;
-      qemu.package = lib.mkForce (pkgs.qemu_test.override { canokeySupport = true; });
       qemu.options = [ "-device canokey,file=/tmp/canokey-file" ];
     };
     boot.loader.systemd-boot.enable = true;
diff --git a/nixos/tests/systemd-resolved.nix b/nixos/tests/systemd-resolved.nix
new file mode 100644
index 0000000000000..3eedc17f4b34f
--- /dev/null
+++ b/nixos/tests/systemd-resolved.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-resolved";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes.server = { lib, config, ... }: let
+    exampleZone = pkgs.writeTextDir "example.com.zone" ''
+      @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
+      @       A       ${(lib.head config.networking.interfaces.eth1.ipv4.addresses).address}
+      @       AAAA    ${(lib.head config.networking.interfaces.eth1.ipv6.addresses).address}
+    '';
+  in {
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+
+    networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+      { address = "fd00::1"; prefixLength = 64; }
+    ];
+
+    services.knot = {
+      enable = true;
+      settings = {
+        server.listen = [
+          "0.0.0.0@53"
+          "::@53"
+        ];
+        template.default.storage = exampleZone;
+        zone."example.com".file = "example.com.zone";
+      };
+    };
+  };
+
+  nodes.client = { nodes, ... }: let
+    inherit (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses) address;
+  in {
+    networking.nameservers = [ address ];
+    networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+      { address = "fd00::2"; prefixLength = 64; }
+    ];
+    services.resolved.enable = true;
+    services.resolved.fallbackDns = [ ];
+    networking.useNetworkd = true;
+    networking.useDHCP = false;
+    systemd.network.networks."40-eth0".enable = false;
+
+    testing.initrdBackdoor = true;
+    boot.initrd = {
+      systemd.enable = true;
+      systemd.initrdBin = [ pkgs.iputils ];
+      network.enable = true;
+      services.resolved.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    address4 = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
+    address6 = (lib.head nodes.server.networking.interfaces.eth1.ipv6.addresses).address;
+  in ''
+    start_all()
+    server.wait_for_unit("multi-user.target")
+
+    def test_client():
+        query = client.succeed("resolvectl query example.com")
+        assert "${address4}" in query
+        assert "${address6}" in query
+        client.succeed("ping -4 -c 1 example.com")
+        client.succeed("ping -6 -c 1 example.com")
+
+    client.wait_for_unit("initrd.target")
+    test_client()
+    client.switch_root()
+
+    client.wait_for_unit("multi-user.target")
+    test_client()
+  '';
+})
diff --git a/nixos/tests/tigervnc.nix b/nixos/tests/tigervnc.nix
index ed575682d9338..b80cb49519c45 100644
--- a/nixos/tests/tigervnc.nix
+++ b/nixos/tests/tigervnc.nix
@@ -38,16 +38,18 @@ makeTest {
     server.succeed("Xvnc -geometry 720x576 :1 -PasswordFile vncpasswd >&2 &")
     server.wait_until_succeeds("nc -z localhost 5901", timeout=10)
     server.succeed("DISPLAY=:1 xwininfo -root | grep 720x576")
-    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC WORLD' >&2 &")
+    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC' >&2 &")
 
     client.wait_for_x()
     client.execute("vncviewer server:1 -PasswordFile vncpasswd >&2 &")
     client.wait_for_window(r"VNC")
     client.screenshot("screenshot")
     text = client.get_screen_text()
+
     # Displayed text
-    assert 'HELLO VNC WORLD' in text
+    assert 'HELLO VNC' in text
     # Client window title
-    assert 'TigerVNC' in text
+    # get_screen_text can't get correct string from screenshot
+    # assert 'TigerVNC' in text
   '';
 }
diff --git a/nixos/tests/vector.nix b/nixos/tests/vector.nix
deleted file mode 100644
index a55eb4e012c5b..0000000000000
--- a/nixos/tests/vector.nix
+++ /dev/null
@@ -1,37 +0,0 @@
-{ system ? builtins.currentSystem, config ? { }
-, pkgs ? import ../.. { inherit system config; } }:
-
-with import ../lib/testing-python.nix { inherit system pkgs; };
-with pkgs.lib;
-
-{
-  test1 = makeTest {
-    name = "vector-test1";
-    meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
-
-    nodes.machine = { config, pkgs, ... }: {
-      services.vector = {
-        enable = true;
-        journaldAccess = true;
-        settings = {
-          sources.journald.type = "journald";
-
-          sinks = {
-            file = {
-              type = "file";
-              inputs = [ "journald" ];
-              path = "/var/lib/vector/logs.log";
-              encoding = { codec = "json"; };
-            };
-          };
-        };
-      };
-    };
-
-    # ensure vector is forwarding the messages appropriately
-    testScript = ''
-      machine.wait_for_unit("vector.service")
-      machine.wait_for_file("/var/lib/vector/logs.log")
-    '';
-  };
-}
diff --git a/nixos/tests/vector/api.nix b/nixos/tests/vector/api.nix
new file mode 100644
index 0000000000000..8aa3a0c1b771f
--- /dev/null
+++ b/nixos/tests/vector/api.nix
@@ -0,0 +1,39 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-api";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes.machineapi = { config, pkgs, ... }: {
+    services.vector = {
+      enable = true;
+      journaldAccess = false;
+      settings = {
+        api.enabled = true;
+
+        sources = {
+          demo_logs = {
+            type = "demo_logs";
+            format = "json";
+          };
+        };
+
+        sinks = {
+          file = {
+            type = "file";
+            inputs = [ "demo_logs" ];
+            path = "/var/lib/vector/logs.log";
+            encoding = { codec = "json"; };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machineapi.wait_for_unit("vector")
+    machineapi.wait_for_open_port(8686)
+    machineapi.succeed("journalctl -o cat -u vector.service | grep 'API server running'")
+    machineapi.wait_until_succeeds("curl -sSf http://localhost:8686/health")
+  '';
+})
diff --git a/nixos/tests/vector/default.nix b/nixos/tests/vector/default.nix
new file mode 100644
index 0000000000000..990b067e81774
--- /dev/null
+++ b/nixos/tests/vector/default.nix
@@ -0,0 +1,11 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+{
+  file-sink = import ./file-sink.nix { inherit system pkgs; };
+  api = import ./api.nix { inherit system pkgs; };
+  dnstap = import ./dnstap.nix { inherit system pkgs; };
+  nginx-clickhouse = import ./nginx-clickhouse.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/vector/dnstap.nix b/nixos/tests/vector/dnstap.nix
new file mode 100644
index 0000000000000..15d643311b604
--- /dev/null
+++ b/nixos/tests/vector/dnstap.nix
@@ -0,0 +1,118 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  dnstapSocket = "/var/run/vector/dnstap.sock";
+in
+{
+  name = "vector-dnstap";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes = {
+    unbound = { config, pkgs, ... }: {
+      networking.firewall.allowedUDPPorts = [ 53 ];
+
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            dnstap = {
+              type = "dnstap";
+              multithreaded = true;
+              mode = "unix";
+              lowercase_hostnames = true;
+              socket_file_mode = 504;
+              socket_path = "${dnstapSocket}";
+            };
+          };
+
+          sinks = {
+            file = {
+              type = "file";
+              inputs = [ "dnstap" ];
+              path = "/var/lib/vector/logs.log";
+              encoding = { codec = "json"; };
+            };
+          };
+        };
+      };
+
+      systemd.services.vector.serviceConfig = {
+        RuntimeDirectory = "vector";
+        RuntimeDirectoryMode = "0770";
+      };
+
+      services.unbound = {
+        enable = true;
+        enableRootTrustAnchor = false;
+        package = pkgs.unbound-full;
+        settings = {
+          server = {
+            interface = [ "0.0.0.0" "::" ];
+            access-control = [ "192.168.1.0/24 allow" ];
+
+            domain-insecure = "local";
+            private-domain = "local";
+
+            local-zone = "local. static";
+            local-data = [
+              ''"test.local. 10800 IN A 192.168.123.5"''
+            ];
+          };
+
+          dnstap = {
+            dnstap-enable = "yes";
+            dnstap-socket-path = "${dnstapSocket}";
+            dnstap-send-identity = "yes";
+            dnstap-send-version = "yes";
+            dnstap-log-client-query-messages = "yes";
+            dnstap-log-client-response-messages = "yes";
+          };
+        };
+      };
+
+      systemd.services.unbound = {
+        after = [ "vector.service" ];
+        wants = [ "vector.service" ];
+        serviceConfig = {
+          # DNSTAP access
+          ReadWritePaths = [ "/var/run/vector" ];
+          SupplementaryGroups = [ "vector" ];
+        };
+      };
+    };
+
+    dnsclient = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.dig ];
+    };
+  };
+
+  testScript = ''
+    unbound.wait_for_unit("unbound")
+    unbound.wait_for_unit("vector")
+
+    unbound.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Socket permissions updated to 0o770'"
+    )
+    unbound.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'component_type=dnstap' | grep 'Listening... path=\"${dnstapSocket}\"'"
+    )
+
+    unbound.wait_for_file("${dnstapSocket}")
+    unbound.succeed("test 770 -eq $(stat -c '%a' ${dnstapSocket})")
+
+    dnsclient.wait_for_unit("network-online.target")
+    dnsclient.succeed(
+      "dig @unbound test.local"
+    )
+
+    unbound.wait_for_file("/var/lib/vector/logs.log")
+
+    unbound.wait_until_succeeds(
+      "grep ClientQuery /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rcodeName\":\"NoError\"'"
+    )
+    unbound.wait_until_succeeds(
+      "grep ClientResponse /var/lib/vector/logs.log | grep '\"domainName\":\"test.local.\"' | grep '\"rData\":\"192.168.123.5\"'"
+    )
+  '';
+})
diff --git a/nixos/tests/vector/file-sink.nix b/nixos/tests/vector/file-sink.nix
new file mode 100644
index 0000000000000..2220d20ac55c3
--- /dev/null
+++ b/nixos/tests/vector/file-sink.nix
@@ -0,0 +1,49 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-test1";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.vector = {
+      enable = true;
+      journaldAccess = true;
+      settings = {
+        sources = {
+          journald.type = "journald";
+
+          vector_metrics.type = "internal_metrics";
+
+          vector_logs.type = "internal_logs";
+        };
+
+        sinks = {
+          file = {
+            type = "file";
+            inputs = [ "journald" "vector_logs" ];
+            path = "/var/lib/vector/logs.log";
+            encoding = { codec = "json"; };
+          };
+
+          prometheus_exporter = {
+            type = "prometheus_exporter";
+            inputs = [ "vector_metrics" ];
+            address = "[::]:9598";
+          };
+        };
+      };
+    };
+  };
+
+  # ensure vector is forwarding the messages appropriately
+  testScript = ''
+    machine.wait_for_unit("vector.service")
+    machine.wait_for_open_port(9598)
+    machine.wait_until_succeeds("journalctl -o cat -u vector.service | grep 'version=\"${pkgs.vector.version}\"'")
+    machine.wait_until_succeeds("journalctl -o cat -u vector.service | grep 'API is disabled'")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_build_info")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_component_received_bytes_total | grep journald")
+    machine.wait_until_succeeds("curl -sSf http://localhost:9598/metrics | grep vector_utilization | grep prometheus_exporter")
+    machine.wait_for_file("/var/lib/vector/logs.log")
+  '';
+})
diff --git a/nixos/tests/vector/nginx-clickhouse.nix b/nixos/tests/vector/nginx-clickhouse.nix
new file mode 100644
index 0000000000000..3d99bac6ac161
--- /dev/null
+++ b/nixos/tests/vector/nginx-clickhouse.nix
@@ -0,0 +1,168 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "vector-nginx-clickhouse";
+  meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+  nodes = {
+    clickhouse = { config, pkgs, ... }: {
+      virtualisation.memorySize = 4096;
+
+      # Clickhouse module can't listen on a non-loopback IP.
+      networking.firewall.allowedTCPPorts = [ 6000 ];
+      services.clickhouse.enable = true;
+
+      # Exercise Vector sink->source for now.
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            vector_source = {
+              type = "vector";
+              address = "[::]:6000";
+            };
+          };
+
+          sinks = {
+            clickhouse = {
+              type = "clickhouse";
+              inputs = [ "vector_source" ];
+              endpoint = "http://localhost:8123";
+              database = "nginxdb";
+              table = "access_logs";
+              skip_unknown_fields = true;
+            };
+          };
+        };
+      };
+    };
+
+    nginx = { config, pkgs, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {};
+      };
+
+      services.vector = {
+        enable = true;
+
+        settings = {
+          sources = {
+            nginx_logs = {
+              type = "file";
+              include = [ "/var/log/nginx/access.log" ];
+              read_from = "end";
+            };
+          };
+
+          sinks = {
+            vector_sink = {
+              type = "vector";
+              inputs = [ "nginx_logs" ];
+              address = "clickhouse:6000";
+            };
+          };
+        };
+      };
+
+      systemd.services.vector.serviceConfig = {
+        SupplementaryGroups = [ "nginx" ];
+      };
+    };
+  };
+
+  testScript =
+  let
+    # work around quote/substitution complexity by Nix, Perl, bash and SQL.
+    databaseDDL = pkgs.writeText "database.sql" "CREATE DATABASE IF NOT EXISTS nginxdb";
+
+    tableDDL = pkgs.writeText "table.sql" ''
+      CREATE TABLE IF NOT EXISTS  nginxdb.access_logs (
+        message String
+      )
+      ENGINE = MergeTree()
+      ORDER BY tuple()
+    '';
+
+    # Graciously taken from https://clickhouse.com/docs/en/integrations/vector
+    tableView = pkgs.writeText "table-view.sql" ''
+      CREATE MATERIALIZED VIEW nginxdb.access_logs_view
+      (
+        RemoteAddr String,
+        Client String,
+        RemoteUser String,
+        TimeLocal DateTime,
+        RequestMethod String,
+        Request String,
+        HttpVersion String,
+        Status Int32,
+        BytesSent Int64,
+        UserAgent String
+      )
+      ENGINE = MergeTree()
+      ORDER BY RemoteAddr
+      POPULATE AS
+      WITH
+       splitByWhitespace(message) as split,
+       splitByRegexp('\S \d+ "([^"]*)"', message) as referer
+      SELECT
+        split[1] AS RemoteAddr,
+        split[2] AS Client,
+        split[3] AS RemoteUser,
+        parseDateTimeBestEffort(replaceOne(trim(LEADING '[' FROM split[4]), ':', ' ')) AS TimeLocal,
+        trim(LEADING '"' FROM split[6]) AS RequestMethod,
+        split[7] AS Request,
+        trim(TRAILING '"' FROM split[8]) AS HttpVersion,
+        split[9] AS Status,
+        split[10] AS BytesSent,
+        trim(BOTH '"' from referer[2]) AS UserAgent
+      FROM
+        (SELECT message FROM nginxdb.access_logs)
+    '';
+
+    selectQuery = pkgs.writeText "select.sql" "SELECT * from nginxdb.access_logs_view";
+  in
+  ''
+    clickhouse.wait_for_unit("clickhouse")
+    clickhouse.wait_for_open_port(8123)
+
+    clickhouse.wait_until_succeeds(
+      "journalctl -o cat -u clickhouse.service | grep 'Started ClickHouse server'"
+    )
+
+    clickhouse.wait_for_unit("vector")
+    clickhouse.wait_for_open_port(6000)
+
+    clickhouse.succeed(
+      "cat ${databaseDDL} | clickhouse-client"
+    )
+
+    clickhouse.succeed(
+      "cat ${tableDDL} | clickhouse-client"
+    )
+
+    clickhouse.succeed(
+      "cat ${tableView} | clickhouse-client"
+    )
+
+    nginx.wait_for_unit("nginx")
+    nginx.wait_for_open_port(80)
+    nginx.wait_for_unit("vector")
+    nginx.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Starting file server'"
+    )
+
+    nginx.succeed("curl http://localhost/")
+    nginx.succeed("curl http://localhost/")
+
+    nginx.wait_for_file("/var/log/nginx/access.log")
+    nginx.wait_until_succeeds(
+      "journalctl -o cat -u vector.service | grep 'Found new file to watch. file=/var/log/nginx/access.log'"
+    )
+
+    clickhouse.wait_until_succeeds(
+      "cat ${selectQuery} | clickhouse-client | grep 'curl'"
+    )
+  '';
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 3c2a391233dbd..5fce3ba548123 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -98,7 +98,6 @@ let
     cfg = (import ../lib/eval-config.nix {
       system = if use64bitGuest then "x86_64-linux" else "i686-linux";
       modules = [
-        ../modules/profiles/minimal.nix
         (testVMConfig vmName vmScript)
       ];
     }).config;
diff --git a/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix b/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix
new file mode 100644
index 0000000000000..3bb9d1e62aefe
--- /dev/null
+++ b/nixos/tests/web-apps/nextjs-ollama-llm-ui.nix
@@ -0,0 +1,22 @@
+{ lib, ... }:
+
+{
+  name = "nextjs-ollama-llm-ui";
+  meta.maintainers = with lib.maintainers; [ malteneuss ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.nextjs-ollama-llm-ui = {
+        enable = true;
+        port = 8080;
+      };
+    };
+
+  testScript = ''
+    # Ensure the service is started and reachable
+    machine.wait_for_unit("nextjs-ollama-llm-ui.service")
+    machine.wait_for_open_port(8080)
+    machine.succeed("curl --fail http://127.0.0.1:8080")
+  '';
+}
diff --git a/nixos/tests/web-apps/pretalx.nix b/nixos/tests/web-apps/pretalx.nix
index 76e261b2207ec..cbb6580aa0515 100644
--- a/nixos/tests/web-apps/pretalx.nix
+++ b/nixos/tests/web-apps/pretalx.nix
@@ -5,13 +5,16 @@
   meta.maintainers = lib.teams.c3d2.members;
 
   nodes = {
-    pretalx = {
+    pretalx = { config, ... }: {
       networking.extraHosts = ''
         127.0.0.1 talks.local
       '';
 
       services.pretalx = {
         enable = true;
+        plugins = with config.services.pretalx.package.plugins; [
+          pages
+        ];
         nginx.domain = "talks.local";
         settings = {
           site.url = "http://talks.local";
diff --git a/nixos/tests/your_spotify.nix b/nixos/tests/your_spotify.nix
new file mode 100644
index 0000000000000..a1fa0e459a8e1
--- /dev/null
+++ b/nixos/tests/your_spotify.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({pkgs, ...}: {
+  name = "your_spotify";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [patrickdag];
+  };
+
+  nodes.machine = {
+    services.your_spotify = {
+      enable = true;
+      spotifySecretFile = pkgs.writeText "spotifySecretFile" "deadbeef";
+      settings = {
+        CLIENT_ENDPOINT = "http://localhost";
+        API_ENDPOINT = "http://localhost:3000";
+        SPOTIFY_PUBLIC = "beefdead";
+      };
+      enableLocalDB = true;
+      nginxVirtualHost = "localhost";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("your_spotify.service")
+
+    machine.wait_for_open_port(3000)
+    machine.wait_for_open_port(80)
+
+    out = machine.succeed("curl --fail -X GET 'http://localhost:3000/'")
+    assert "Hello !" in out
+
+    out = machine.succeed("curl --fail -X GET 'http://localhost:80/'")
+    assert "<title>Your Spotify</title>" in out
+  '';
+})