about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/lib/make-iso9660-image.nix25
-rw-r--r--nixos/lib/make-iso9660-image.sh5
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix11
-rw-r--r--nixos/modules/services/databases/lldap.nix17
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix (renamed from nixos/modules/services/x11/desktop-managers/plasma6.nix)15
-rw-r--r--nixos/modules/services/networking/networkmanager.nix1
-rw-r--r--nixos/modules/services/networking/unbound.nix7
-rw-r--r--nixos/modules/services/security/esdm.nix101
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/system/boot/uki.nix2
-rw-r--r--nixos/modules/virtualisation/incus.nix13
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/incus/default.nix1
-rw-r--r--nixos/tests/incus/openvswitch.nix65
-rw-r--r--nixos/tests/miriway.nix4
-rw-r--r--nixos/tests/ollama.nix56
16 files changed, 224 insertions, 102 deletions
diff --git a/nixos/lib/make-iso9660-image.nix b/nixos/lib/make-iso9660-image.nix
index 2f7dcf519a16..ec520f570682 100644
--- a/nixos/lib/make-iso9660-image.nix
+++ b/nixos/lib/make-iso9660-image.nix
@@ -1,4 +1,4 @@
-{ stdenv, closureInfo, xorriso, syslinux, libossp_uuid
+{ lib, stdenv, callPackage, closureInfo, xorriso, syslinux, libossp_uuid, squashfsTools
 
 , # The file name of the resulting ISO image.
   isoName ? "cd.iso"
@@ -16,6 +16,17 @@
   # symlink to `object' that will be added to the CD.
   storeContents ? []
 
+, # In addition to `contents', the closure of the store paths listed
+  # in `squashfsContents' is compressed as squashfs and the result is
+  # placed in /nix-store.squashfs on the CD.
+  # FIXME: This is a performance optimization to avoid Hydra copying
+  # the squashfs between builders and should be removed when Hydra
+  # is smarter about scheduling.
+  squashfsContents ? []
+
+, # Compression settings for squashfs
+  squashfsCompression ? "xz -Xdict-size 100%"
+
 , # Whether this should be an El-Torito bootable CD.
   bootable ? false
 
@@ -45,12 +56,20 @@ assert bootable -> bootImage != "";
 assert efiBootable -> efiBootImage != "";
 assert usbBootable -> isohybridMbrImage != "";
 
+let
+  needSquashfs = squashfsContents != [];
+  makeSquashfsDrv = callPackage ./make-squashfs.nix {
+    storeContents = squashfsContents;
+    comp = squashfsCompression;
+  };
+in
 stdenv.mkDerivation {
   name = isoName;
   __structuredAttrs = true;
 
   buildCommandPath = ./make-iso9660-image.sh;
-  nativeBuildInputs = [ xorriso syslinux zstd libossp_uuid ];
+  nativeBuildInputs = [ xorriso syslinux zstd libossp_uuid ]
+    ++ lib.optionals needSquashfs makeSquashfsDrv.nativeBuildInputs;
 
   inherit isoName bootable bootImage compressImage volumeID efiBootImage efiBootable isohybridMbrImage usbBootable;
 
@@ -60,6 +79,8 @@ stdenv.mkDerivation {
   objects = map (x: x.object) storeContents;
   symlinks = map (x: x.symlink) storeContents;
 
+  squashfsCommand = lib.optionalString needSquashfs makeSquashfsDrv.buildCommand;
+
   # For obtaining the closure of `storeContents'.
   closureInfo = closureInfo { rootPaths = map (x: x.object) storeContents; };
 }
diff --git a/nixos/lib/make-iso9660-image.sh b/nixos/lib/make-iso9660-image.sh
index 34febe9cfe0e..5881195e461f 100644
--- a/nixos/lib/make-iso9660-image.sh
+++ b/nixos/lib/make-iso9660-image.sh
@@ -68,6 +68,11 @@ for i in $(< $closureInfo/store-paths); do
     addPath "${i:1}" "$i"
 done
 
+# If needed, build a squashfs and add that
+if [[ -n "$squashfsCommand" ]]; then
+    (out="nix-store.squashfs" eval "$squashfsCommand")
+    addPath "nix-store.squashfs" "nix-store.squashfs"
+fi
 
 # Also include a manifest of the closures in a format suitable for
 # nix-store --load-db.
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 6adb94e09aff..f5b6af3a6b7f 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -811,12 +811,6 @@ in
       optional config.isoImage.includeSystemBuildDependencies
         config.system.build.toplevel.drvPath;
 
-    # Create the squashfs image that contains the Nix store.
-    system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
-      storeContents = config.isoImage.storeContents;
-      comp = config.isoImage.squashfsCompression;
-    };
-
     # Individual files to be included on the CD, outside of the Nix
     # store on the CD.
     isoImage.contents =
@@ -827,9 +821,6 @@ in
         { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
           target = "/boot/" + config.system.boot.loader.initrdFile;
         }
-        { source = config.system.build.squashfsStore;
-          target = "/nix-store.squashfs";
-        }
         { source = pkgs.writeText "version" config.system.nixos.label;
           target = "/version.txt";
         }
@@ -878,6 +869,8 @@ in
       bootable = config.isoImage.makeBiosBootable;
       bootImage = "/isolinux/isolinux.bin";
       syslinux = if config.isoImage.makeBiosBootable then pkgs.syslinux else null;
+      squashfsContents = config.isoImage.storeContents;
+      squashfsCompression = config.isoImage.squashfsCompression;
     } // optionalAttrs (config.isoImage.makeUsbBootable && config.isoImage.makeBiosBootable) {
       usbBootable = true;
       isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
diff --git a/nixos/modules/services/databases/lldap.nix b/nixos/modules/services/databases/lldap.nix
index e821da8e58aa..033de7af886f 100644
--- a/nixos/modules/services/databases/lldap.nix
+++ b/nixos/modules/services/databases/lldap.nix
@@ -107,10 +107,25 @@ in
       wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
+      # lldap defaults to a hardcoded `jwt_secret` value if none is provided, which is bad, because
+      # an attacker could create a valid admin jwt access token fairly trivially.
+      # Because there are 3 different ways `jwt_secret` can be provided, we check if any one of them is present,
+      # and if not, bootstrap a secret in `/var/lib/lldap/jwt_secret_file` and give that to lldap.
+      script = lib.optionalString (!cfg.settings ? jwt_secret) ''
+        if [[ -z "$LLDAP_JWT_SECRET_FILE" ]] && [[ -z "$LLDAP_JWT_SECRET" ]]; then
+          if [[ ! -e "./jwt_secret_file" ]]; then
+            ${lib.getExe pkgs.openssl} rand -base64 -out ./jwt_secret_file 32
+          fi
+          export LLDAP_JWT_SECRET_FILE="./jwt_secret_file"
+        fi
+      '' + ''
+         ${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}
+      '';
       serviceConfig = {
-        ExecStart = "${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}";
         StateDirectory = "lldap";
+        StateDirectoryMode = "0750";
         WorkingDirectory = "%S/lldap";
+        UMask = "0027";
         User = "lldap";
         Group = "lldap";
         DynamicUser = true;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index a471a48c9002..1710d28954d6 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -5,8 +5,7 @@
   utils,
   ...
 }: let
-  xcfg = config.services.xserver;
-  cfg = xcfg.desktopManager.plasma6;
+  cfg = config.services.desktopManager.plasma6;
 
   inherit (pkgs) kdePackages;
   inherit (lib) literalExpression mkDefault mkIf mkOption mkPackageOptionMD types;
@@ -17,7 +16,7 @@
   '';
 in {
   options = {
-    services.xserver.desktopManager.plasma6 = {
+    services.desktopManager.plasma6 = {
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -44,6 +43,12 @@ in {
     };
   };
 
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma6" "enable" ] [ "services" "desktopManager" "plasma6" "enable" ])
+    (lib.mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma6" "enableQt5Integration" ] [ "services" "desktopManager" "plasma6" "enableQt5Integration" ])
+    (lib.mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma6" "notoPackage" ] [ "services" "desktopManager" "plasma6" "notoPackage" ])
+  ];
+
   config = mkIf cfg.enable {
     assertions = [
       {
@@ -161,7 +166,7 @@ in {
     in
       requiredPackages
       ++ utils.removePackagesByName optionalPackages config.environment.plasma6.excludePackages
-      ++ lib.optionals config.services.xserver.desktopManager.plasma6.enableQt5Integration [
+      ++ lib.optionals config.services.desktopManager.plasma6.enableQt5Integration [
         breeze.qt5
         plasma-integration.qt5
         pkgs.plasma5Packages.kwayland-integration
@@ -185,7 +190,7 @@ in {
       "/libexec" # for drkonqi
     ];
 
-    environment.etc."X11/xkb".source = xcfg.xkb.dir;
+    environment.etc."X11/xkb".source = config.services.xserver.xkb.dir;
 
     # Add ~/.config/kdedefaults to XDG_CONFIG_DIRS for shells, since Plasma sets that.
     # FIXME: maybe we should append to XDG_CONFIG_DIRS in /etc/set-environment instead?
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index c96439cf2641..dcde505b7f2a 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -436,6 +436,7 @@ in
             And if you edit a declarative profile NetworkManager will move it to the persistent storage and treat it like a ad-hoc one,
             but there will be two profiles as soon as the systemd unit from this option runs again which can be confusing since NetworkManager tools will start displaying two profiles with the same name and probably a bit different settings depending on what you edited.
             A profile won't be deleted even if it's removed from the config until the system reboots because that's when NetworkManager clears it's temp directory.
+            If `networking.resolvconf.enable` is true, attributes affecting the name resolution (such as `ignore-auto-dns`) may not end up changing `/etc/resolv.conf` as expected when other name services (for example `networking.dhcpcd`) are enabled. Run `resolvconf -l` in the terminal to see what each service produces.
           '';
         };
         environmentFiles = mkOption {
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 8438e472e11e..17c6789827b9 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -76,12 +76,13 @@ in {
 
       checkconf = mkOption {
         type = types.bool;
-        default = !cfg.settings ? include;
-        defaultText = "!config.services.unbound.settings ? include";
+        default = !cfg.settings ? include && !cfg.settings ? remote-control;
+        defaultText = "!services.unbound.settings ? include && !services.unbound.settings ? remote-control";
         description = lib.mdDoc ''
           Wether to check the resulting config file with unbound checkconf for syntax errors.
 
-          If settings.include is used, then this options is disabled, as the import can likely not be resolved at build time.
+          If settings.include is used, this options is disabled, as the import can likely not be accessed at build time.
+          If settings.remote-control is used, this option is disabled, too as the control-key-file, server-cert-file and server-key-file cannot be accessed at build time.
         '';
       };
 
diff --git a/nixos/modules/services/security/esdm.nix b/nixos/modules/services/security/esdm.nix
index 134b4be1a94c..c34fba1b3c75 100644
--- a/nixos/modules/services/security/esdm.nix
+++ b/nixos/modules/services/security/esdm.nix
@@ -4,49 +4,33 @@ let
   cfg = config.services.esdm;
 in
 {
+  imports = [
+    # removed option 'services.esdm.cuseRandomEnable'
+    (lib.mkRemovedOptionModule [ "services" "esdm" "cuseRandomEnable" ] ''
+      Use services.esdm.enableLinuxCompatServices instead.
+    '')
+    # removed option 'services.esdm.cuseUrandomEnable'
+    (lib.mkRemovedOptionModule [ "services" "esdm" "cuseUrandomEnable" ] ''
+      Use services.esdm.enableLinuxCompatServices instead.
+    '')
+    # removed option 'services.esdm.procEnable'
+    (lib.mkRemovedOptionModule [ "services" "esdm" "procEnable" ] ''
+      Use services.esdm.enableLinuxCompatServices instead.
+    '')
+    # removed option 'services.esdm.verbose'
+    (lib.mkRemovedOptionModule [ "services" "esdm" "verbose" ] ''
+      There is no replacement.
+    '')
+  ];
+
   options.services.esdm = {
     enable = lib.mkEnableOption (lib.mdDoc "ESDM service configuration");
     package = lib.mkPackageOption pkgs "esdm" { };
-    serverEnable = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Enable option for ESDM server service. If serverEnable == false, then the esdm-server
-        will not start. Also the subsequent services esdm-cuse-random, esdm-cuse-urandom
-        and esdm-proc will not start as these have the entry Want=esdm-server.service.
-      '';
-    };
-    cuseRandomEnable = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Enable option for ESDM cuse-random service. Determines if the esdm-cuse-random.service
-        is started.
-      '';
-    };
-    cuseUrandomEnable = lib.mkOption {
-      type = lib.types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Enable option for ESDM cuse-urandom service. Determines if the esdm-cuse-urandom.service
-        is started.
-      '';
-    };
-    procEnable = lib.mkOption {
+    enableLinuxCompatServices = lib.mkOption {
       type = lib.types.bool;
       default = true;
       description = lib.mdDoc ''
-        Enable option for ESDM proc service. Determines if the esdm-proc.service
-        is started.
-      '';
-    };
-    verbose = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable verbose ExecStart for ESDM. If verbose == true, then the corresponding "ExecStart"
-        values of the 4 aforementioned services are overwritten with the option
-        for the highest verbosity.
+        Enable /dev/random, /dev/urandom and /proc/sys/kernel/random/* userspace wrapper.
       '';
     };
   };
@@ -55,46 +39,13 @@ in
     lib.mkMerge [
       ({
         systemd.packages = [ cfg.package ];
-      })
-      # It is necessary to set those options for these services to be started by systemd in NixOS
-      (lib.mkIf cfg.serverEnable {
         systemd.services."esdm-server".wantedBy = [ "basic.target" ];
-        systemd.services."esdm-server".serviceConfig = lib.mkIf cfg.verbose {
-          ExecStart = [
-            " " # unset previous value defined in 'esdm-server.service'
-            "${cfg.package}/bin/esdm-server -f -vvvvvv"
-          ];
-        };
-      })
-
-      (lib.mkIf cfg.cuseRandomEnable {
-        systemd.services."esdm-cuse-random".wantedBy = [ "basic.target" ];
-        systemd.services."esdm-cuse-random".serviceConfig = lib.mkIf cfg.verbose {
-          ExecStart = [
-            " " # unset previous value defined in 'esdm-cuse-random.service'
-            "${cfg.package}/bin/esdm-cuse-random -f -v 6"
-          ];
-        };
       })
-
-      (lib.mkIf cfg.cuseUrandomEnable {
-        systemd.services."esdm-cuse-urandom".wantedBy = [ "basic.target" ];
-        systemd.services."esdm-cuse-urandom".serviceConfig = lib.mkIf cfg.verbose {
-          ExecStart = [
-            " " # unset previous value defined in 'esdm-cuse-urandom.service'
-            "${config.services.esdm.package}/bin/esdm-cuse-urandom -f -v 6"
-          ];
-        };
-      })
-
-      (lib.mkIf cfg.procEnable {
-        systemd.services."esdm-proc".wantedBy = [ "basic.target" ];
-        systemd.services."esdm-proc".serviceConfig = lib.mkIf cfg.verbose {
-          ExecStart = [
-            " " # unset previous value defined in 'esdm-proc.service'
-            "${cfg.package}/bin/esdm-proc --relabel -f -o allow_other /proc/sys/kernel/random -v 6"
-          ];
-        };
+      # It is necessary to set those options for these services to be started by systemd in NixOS
+      (lib.mkIf cfg.enableLinuxCompatServices {
+        systemd.targets."esdm-linux-compat".wantedBy = [ "basic.target" ];
+        systemd.services."esdm-server-suspend".wantedBy = [ "sleep.target" "suspend.target" "hibernate.target" ];
+        systemd.services."esdm-server-resume".wantedBy = [ "sleep.target" "suspend.target" "hibernate.target" ];
       })
     ]);
 
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index ecb8d1e91bde..33d0a7b52643 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -18,7 +18,7 @@ in
   # determines the default: later modules (if enabled) are preferred.
   # E.g., if Plasma 5 is enabled, it supersedes xterm.
   imports = [
-    ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./plasma6.nix ./lumina.nix
+    ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ../../desktop-managers/plasma6.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome.nix ./retroarch.nix ./kodi.nix
     ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
     ./cinnamon.nix ./budgie.nix ./deepin.nix
diff --git a/nixos/modules/system/boot/uki.nix b/nixos/modules/system/boot/uki.nix
index ce00ac8e6397..0965b887c12e 100644
--- a/nixos/modules/system/boot/uki.nix
+++ b/nixos/modules/system/boot/uki.nix
@@ -75,6 +75,8 @@ in
         OSRelease = lib.mkOptionDefault "@${config.system.build.etc}/etc/os-release";
         # This is needed for cross compiling.
         EFIArch = lib.mkOptionDefault efiArch;
+      } // lib.optionalAttrs (config.hardware.deviceTree.enable && config.hardware.deviceTree.name != null) {
+        DeviceTree = lib.mkOptionDefault "${config.hardware.deviceTree.package}/${config.hardware.deviceTree.name}";
       };
     };
 
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
index a561c5682ae5..da7873c7bec8 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -164,19 +164,24 @@ in
         "network-online.target"
         "lxcfs.service"
         "incus.socket"
-      ];
+      ]
+        ++ lib.optional config.virtualisation.vswitch.enable "ovs-vswitchd.service";
+
       requires = [
         "lxcfs.service"
         "incus.socket"
-      ];
+      ]
+        ++ lib.optional config.virtualisation.vswitch.enable "ovs-vswitchd.service";
+
       wants = [
         "network-online.target"
       ];
 
-      path = lib.mkIf config.boot.zfs.enabled [
+      path = lib.optionals config.boot.zfs.enabled [
         config.boot.zfs.package
         "${config.boot.zfs.package}/lib/udev"
-      ];
+      ]
+        ++ lib.optional config.virtualisation.vswitch.enable config.virtualisation.vswitch.package;
 
       environment = lib.mkMerge [ {
         # Override Path to the LXC template configuration directory
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 6c188593a97a..ac64b85dd486 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -640,6 +640,7 @@ in {
   nzbget = handleTest ./nzbget.nix {};
   nzbhydra2 = handleTest ./nzbhydra2.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
+  ollama = handleTest ./ollama.nix {};
   ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
diff --git a/nixos/tests/incus/default.nix b/nixos/tests/incus/default.nix
index ff36fe9d6730..474a621c5ce9 100644
--- a/nixos/tests/incus/default.nix
+++ b/nixos/tests/incus/default.nix
@@ -11,6 +11,7 @@
     boot.initrd.systemd.enable = true;
   }; };
   lxd-to-incus = import ./lxd-to-incus.nix { inherit system pkgs; };
+  openvswitch = import ./openvswitch.nix { inherit system pkgs; };
   preseed = import ./preseed.nix { inherit system pkgs; };
   socket-activated = import ./socket-activated.nix { inherit system pkgs; };
   ui = import ./ui.nix {inherit system pkgs;};
diff --git a/nixos/tests/incus/openvswitch.nix b/nixos/tests/incus/openvswitch.nix
new file mode 100644
index 000000000000..5d4aef031ad0
--- /dev/null
+++ b/nixos/tests/incus/openvswitch.nix
@@ -0,0 +1,65 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "incus-openvswitch";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+      vswitch.enable = true;
+      incus.preseed = {
+        networks = [
+          {
+            name = "nixostestbr0";
+            type = "bridge";
+            config = {
+              "bridge.driver" = "openvswitch";
+              "ipv4.address" = "10.0.100.1/24";
+              "ipv4.nat" = "true";
+            };
+          }
+        ];
+        profiles = [
+          {
+            name = "nixostest_default";
+            devices = {
+              eth0 = {
+                name = "eth0";
+                network = "nixostestbr0";
+                type = "nic";
+              };
+              root = {
+                path = "/";
+                pool = "default";
+                size = "35GiB";
+                type = "disk";
+              };
+            };
+          }
+        ];
+        storage_pools = [
+          {
+            name = "nixostest_pool";
+            driver = "dir";
+          }
+        ];
+      };
+    };
+    networking.nftables.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("incus.service")
+    machine.wait_for_unit("incus-preseed.service")
+
+    with subtest("Verify openvswitch bridge"):
+      machine.succeed("incus network info nixostestbr0")
+
+    with subtest("Verify openvswitch bridge"):
+      machine.succeed("ovs-vsctl br-exists nixostestbr0")
+  '';
+})
diff --git a/nixos/tests/miriway.nix b/nixos/tests/miriway.nix
index a0987d9fc41b..24e6ec6367cd 100644
--- a/nixos/tests/miriway.nix
+++ b/nixos/tests/miriway.nix
@@ -100,7 +100,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     # Test Wayland
     # We let Miriway start the first terminal, as we might get stuck if it's not ready to process the first keybind
     # machine.send_key("ctrl-alt-t")
-    machine.wait_for_text("alice@machine")
+    machine.wait_for_text(r"(alice|machine)")
     machine.send_chars("test-wayland\n")
     machine.wait_for_file("/tmp/test-wayland-exit-ok")
     machine.copy_from_vm("/tmp/test-wayland.out")
@@ -112,7 +112,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
     # Test XWayland
     machine.send_key("ctrl-alt-a")
-    machine.wait_for_text("alice@machine")
+    machine.wait_for_text(r"(alice|machine)")
     machine.send_chars("test-x11\n")
     machine.wait_for_file("/tmp/test-x11-exit-ok")
     machine.copy_from_vm("/tmp/test-x11.out")
diff --git a/nixos/tests/ollama.nix b/nixos/tests/ollama.nix
new file mode 100644
index 000000000000..4b21f445cdbd
--- /dev/null
+++ b/nixos/tests/ollama.nix
@@ -0,0 +1,56 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  mainPort = "11434";
+  altPort = "11435";
+
+  curlRequest = port: request:
+    "curl http://127.0.0.1:${port}/api/generate -d '${builtins.toJSON request}'";
+
+  prompt = {
+    model = "tinydolphin";
+    prompt = "lorem ipsum";
+    options = {
+      seed = 69;
+      temperature = 0;
+    };
+  };
+in
+{
+  name = "ollama";
+  meta = with lib.maintainers; {
+    maintainers = [ abysssol ];
+  };
+
+  nodes = {
+    cpu = { ... }: {
+      services.ollama.enable = true;
+    };
+
+    rocm = { ... }: {
+      services.ollama.enable = true;
+      services.ollama.acceleration = "rocm";
+    };
+
+    cuda = { ... }: {
+      services.ollama.enable = true;
+      services.ollama.acceleration = "cuda";
+    };
+
+    altAddress = { ... }: {
+      services.ollama.enable = true;
+      services.ollama.listenAddress = "127.0.0.1:${altPort}";
+    };
+  };
+
+  testScript = ''
+    vms = [ cpu, rocm, cuda, altAddress ];
+
+    start_all()
+    for vm in vms:
+        vm.wait_for_unit("multi-user.target")
+
+    stdout = cpu.succeed("""${curlRequest mainPort prompt}""", timeout=100)
+
+    stdout = altAddress.succeed("""${curlRequest altPort prompt}""", timeout=100)
+  '';
+})