From 4bfaeecd660880a018b5715f541ae1eb877f5d4c Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 19:24:45 +0200 Subject: Added a local ai stack to herra --- hosts/herra/ai.nix | 51 +++++++++++++++++++++++++++++++++++++++++++ hosts/herra/arion-compose.nix | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 hosts/herra/ai.nix create mode 100644 hosts/herra/arion-compose.nix (limited to 'hosts/herra') diff --git a/hosts/herra/ai.nix b/hosts/herra/ai.nix new file mode 100644 index 0000000..9ca7632 --- /dev/null +++ b/hosts/herra/ai.nix @@ -0,0 +1,51 @@ +{pkgs, ...}: { + virtualisation.docker.enable = true; + + environment.systemPackages = with pkgs; [ + arion + ]; + + services.ollama = { + enable = true; + + host = "0.0.0.0"; + port = 11434; + + # probably won't work reliably on Polaris + # acceleration = "rocm"; + }; + + services.nginx = { + enable = true; + + virtualHosts."ai.local" = { + locations."/" = { + proxyPass = "http://127.0.0.1:7000"; + proxyWebsockets = true; + }; + }; + }; + + environment.etc."arion/arion-compose.nix".source = + ./arion-compose.nix; + + systemd.services.odysseus = { + wantedBy = ["multi-user.target"]; + + after = [ + "docker.service" + "ollama.service" + ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + + WorkingDirectory = "/etc/arion"; + + ExecStart = "${pkgs.arion}/bin/arion up -d"; + + ExecStop = "${pkgs.arion}/bin/arion down"; + }; + }; +} diff --git a/hosts/herra/arion-compose.nix b/hosts/herra/arion-compose.nix new file mode 100644 index 0000000..daa4077 --- /dev/null +++ b/hosts/herra/arion-compose.nix @@ -0,0 +1,50 @@ +{pkgs, ...}: { + project.name = "odysseus"; + + services.odysseus.service = { + image = "ghcr.io/pewdiepie-archdaemon/odysseus:latest"; + + ports = [ + "7000:7000" + ]; + + volumes = [ + "/srv/odysseus/data:/app/data" + ]; + + environment = { + AUTH_ENABLED = "true"; + + LLM_HOST = "host.docker.internal:11434"; + + CHROMADB_HOST = "chromadb"; + + SEARXNG_INSTANCE = "http://searxng:8080"; + }; + + extra_hosts = [ + "host.docker.internal:host-gateway" + ]; + + depends_on = [ + "chromadb" + "searxng" + ]; + }; + + services.chromadb.service = { + image = "chromadb/chroma:latest"; + + volumes = [ + "/srv/odysseus/chroma:/chroma/chroma" + ]; + }; + + services.searxng.service = { + image = "searxng/searxng:latest"; + }; + + services.ntfy.service = { + image = "binwiederhier/ntfy:latest"; + }; +} -- cgit v1.2.3 From c843bfd1e2a746895cb839ab1bedd41764b8e6ab Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 19:26:40 +0200 Subject: Added ai to herra's config aggregator --- hosts/herra/configuration.nix | 1 + 1 file changed, 1 insertion(+) (limited to 'hosts/herra') diff --git a/hosts/herra/configuration.nix b/hosts/herra/configuration.nix index f3df657..4403d12 100644 --- a/hosts/herra/configuration.nix +++ b/hosts/herra/configuration.nix @@ -11,6 +11,7 @@ ./drivers.nix ./steam.nix ./network.nix + ./ai.nix ]; networking.hostName = "herra"; -- cgit v1.2.3 From b4b43c72ec3f82db90a872dd1d1de2c9721fcb37 Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 19:30:44 +0200 Subject: Added arion-pkgs.nix into /etc so that arion doesnt complain --- hosts/herra/ai.nix | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'hosts/herra') diff --git a/hosts/herra/ai.nix b/hosts/herra/ai.nix index 9ca7632..355d31f 100644 --- a/hosts/herra/ai.nix +++ b/hosts/herra/ai.nix @@ -29,6 +29,12 @@ environment.etc."arion/arion-compose.nix".source = ./arion-compose.nix; + environment.etc."arion/arion-pkgs.nix".text = '' + { pkgs ? import {} }: + { + } + ''; + systemd.services.odysseus = { wantedBy = ["multi-user.target"]; -- cgit v1.2.3 From 7cf40a9e058c0d602fc5e21f0447a04ef78144e7 Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 19:42:57 +0200 Subject: Rewrote ai.nix to use native nixos containerisation --- hosts/herra/ai.nix | 81 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 36 deletions(-) (limited to 'hosts/herra') diff --git a/hosts/herra/ai.nix b/hosts/herra/ai.nix index 355d31f..c261506 100644 --- a/hosts/herra/ai.nix +++ b/hosts/herra/ai.nix @@ -1,20 +1,54 @@ {pkgs, ...}: { + # ---------------------------- + # Docker runtime (for UI only) + # ---------------------------- virtualisation.docker.enable = true; + virtualisation.docker.autoPrune.enable = true; - environment.systemPackages = with pkgs; [ - arion - ]; - + # ---------------------------- + # Ollama (native, stable core) + # ---------------------------- services.ollama = { enable = true; - - host = "0.0.0.0"; + host = "127.0.0.1"; port = 11434; + }; + + # ---------------------------- + # Persistent storage + # ---------------------------- + systemd.tmpfiles.rules = [ + "d /var/lib/odysseus 0755 root root -" + ]; + + # ---------------------------- + # Odysseus UI container (minimal) + # ---------------------------- + virtualisation.oci-containers.containers.odysseus = { + image = "ghcr.io/pewdiepie-archdaemon/odysseus:latest"; + + ports = [ + "7000:7000" + ]; + + volumes = [ + "/var/lib/odysseus:/app/data" + ]; + + environment = { + LLM_HOST = "http://host.docker.internal:11434"; + AUTH_ENABLED = "true"; + }; - # probably won't work reliably on Polaris - # acceleration = "rocm"; + extraOptions = [ + "--add-host=host.docker.internal:host-gateway" + "--restart=unless-stopped" + ]; }; + # ---------------------------- + # Optional: reverse proxy (clean URL) + # ---------------------------- services.nginx = { enable = true; @@ -26,32 +60,7 @@ }; }; - environment.etc."arion/arion-compose.nix".source = - ./arion-compose.nix; - - environment.etc."arion/arion-pkgs.nix".text = '' - { pkgs ? import {} }: - { - } - ''; - - systemd.services.odysseus = { - wantedBy = ["multi-user.target"]; - - after = [ - "docker.service" - "ollama.service" - ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - - WorkingDirectory = "/etc/arion"; - - ExecStart = "${pkgs.arion}/bin/arion up -d"; - - ExecStop = "${pkgs.arion}/bin/arion down"; - }; - }; + networking.firewall.allowedTCPPorts = [ + 7000 + ]; } -- cgit v1.2.3 From 6c6c5e1d65f7c5c69a5fe362eb677ff9db9e2d86 Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 20:35:54 +0200 Subject: Moved docker config from herra ai.nix to system/virtualisation.nix --- hosts/herra/ai.nix | 6 ------ system/virtualisation.nix | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'hosts/herra') diff --git a/hosts/herra/ai.nix b/hosts/herra/ai.nix index c261506..ccef5a7 100644 --- a/hosts/herra/ai.nix +++ b/hosts/herra/ai.nix @@ -1,10 +1,4 @@ {pkgs, ...}: { - # ---------------------------- - # Docker runtime (for UI only) - # ---------------------------- - virtualisation.docker.enable = true; - virtualisation.docker.autoPrune.enable = true; - # ---------------------------- # Ollama (native, stable core) # ---------------------------- diff --git a/system/virtualisation.nix b/system/virtualisation.nix index 69c236e..4737e42 100644 --- a/system/virtualisation.nix +++ b/system/virtualisation.nix @@ -1,6 +1,7 @@ {...}: { virtualisation.docker = { enable = true; + autoPrune.enable = true; }; virtualisation.oci-containers = { backend = "docker"; -- cgit v1.2.3 From 80971b27fc79b6edc0693c91f938b358c0f3f543 Mon Sep 17 00:00:00 2001 From: Natasha Moongrave Date: Mon, 1 Jun 2026 22:40:16 +0200 Subject: Fixed some config issues --- hosts/herra/ai.nix | 165 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 25 deletions(-) (limited to 'hosts/herra') diff --git a/hosts/herra/ai.nix b/hosts/herra/ai.nix index ccef5a7..df93a24 100644 --- a/hosts/herra/ai.nix +++ b/hosts/herra/ai.nix @@ -1,60 +1,175 @@ -{pkgs, ...}: { +{ + pkgs, + lib, + ... +}: let + puid = "1000"; + pgid = "100"; + odysseusRepo = "https://github.com/pewdiepie-archdaemon/odysseus.git"; + odysseusDir = "/var/lib/odysseus/src"; +in { # ---------------------------- - # Ollama (native, stable core) + # Ollama (native) # ---------------------------- services.ollama = { enable = true; - host = "127.0.0.1"; + host = "0.0.0.0"; port = 11434; }; + # ---------------------------- + # Docker + # ---------------------------- + virtualisation.docker.enable = true; + virtualisation.oci-containers.backend = "docker"; + # ---------------------------- # Persistent storage # ---------------------------- systemd.tmpfiles.rules = [ - "d /var/lib/odysseus 0755 root root -" + "d /var/lib/odysseus/data 0755 root root -" + "d /var/lib/odysseus/logs 0755 root root -" + "d /var/lib/odysseus/ssh 0755 root root -" + "d /var/lib/odysseus/huggingface 0755 root root -" + "d /var/lib/odysseus/chromadb 0755 root root -" + "d /var/lib/odysseus/ntfy 0755 root root -" + "d /var/lib/odysseus/searxng 0777 root root -" + "d /var/lib/odysseus/src 0755 root root -" ]; # ---------------------------- - # Odysseus UI container (minimal) + # SearXNG config + # ---------------------------- + environment.etc."odysseus/searxng/settings.yml".text = '' + use_default_settings: true + server: + secret_key: "change-me-openssl-rand-hex-32" + limiter: false + image_proxy: true + search: + safe_search: 0 + formats: + - html + - json + ''; + + # ---------------------------- + # Build Odysseus from source via systemd oneshot + # ---------------------------- + systemd.services.odysseus-build = { + description = "Build Odysseus Docker image from source"; + wantedBy = ["docker-odysseus.service"]; + before = ["docker-odysseus.service"]; + after = ["docker.service" "network-online.target"]; + wants = ["network-online.target"]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + path = [pkgs.git pkgs.docker]; + script = '' + if [ ! -d "${odysseusDir}/.git" ]; then + git clone ${odysseusRepo} ${odysseusDir} + else + git -C ${odysseusDir} pull --ff-only + fi + + NEW_HASH=$(git -C ${odysseusDir} rev-parse HEAD) + OLD_HASH=$(cat /var/lib/odysseus/.last-built-commit 2>/dev/null || echo "") + + if [ "$NEW_HASH" != "$OLD_HASH" ] || ! docker image inspect odysseus:local &>/dev/null; then + echo "Building odysseus image at commit $NEW_HASH..." + docker build -t odysseus:local ${odysseusDir} + echo "$NEW_HASH" > /var/lib/odysseus/.last-built-commit + else + echo "Image up to date at $OLD_HASH, skipping build." + fi + ''; + }; + + # ---------------------------- + # Containers # ---------------------------- - virtualisation.oci-containers.containers.odysseus = { - image = "ghcr.io/pewdiepie-archdaemon/odysseus:latest"; + virtualisation.oci-containers.containers = { + odysseus-chromadb = { + image = "chromadb/chroma:latest"; + ports = ["127.0.0.1:8100:8000"]; + volumes = ["/var/lib/odysseus/chromadb:/chroma/chroma"]; + environment.ANONYMIZED_TELEMETRY = "FALSE"; + }; - ports = [ - "7000:7000" - ]; + odysseus-searxng = { + image = "searxng/searxng:latest"; + ports = ["127.0.0.1:8080:8080"]; + volumes = [ + "/var/lib/odysseus/searxng:/etc/searxng" + "/etc/odysseus/searxng/settings.yml:/etc/searxng/settings.yml:ro" + ]; + environment.SEARXNG_BASE_URL = "http://localhost:8080/"; + }; - volumes = [ - "/var/lib/odysseus:/app/data" - ]; + odysseus-ntfy = { + image = "binwiederhier/ntfy"; + cmd = ["serve"]; + ports = ["127.0.0.1:8091:80"]; + volumes = ["/var/lib/odysseus/ntfy:/var/cache/ntfy"]; + environment.NTFY_BASE_URL = "http://localhost:8091"; + }; - environment = { - LLM_HOST = "http://host.docker.internal:11434"; - AUTH_ENABLED = "true"; + odysseus = { + image = "odysseus:local"; + ports = ["127.0.0.1:7000:7000"]; + volumes = [ + "/var/lib/odysseus/data:/app/data" + "/var/lib/odysseus/logs:/app/logs" + "/var/lib/odysseus/ssh:/app/.ssh" + "/var/lib/odysseus/huggingface:/app/.cache/huggingface" + ]; + environment = { + APP_BIND = "0.0.0.0"; + APP_PORT = "7000"; + AUTH_ENABLED = "true"; + PUID = puid; + PGID = pgid; + SEARXNG_INSTANCE = "http://host.docker.internal:8080"; + CHROMADB_HOST = "host.docker.internal"; + CHROMADB_PORT = "8000"; + OLLAMA_BASE_URL = "http://host.docker.internal:11434/v1"; + }; + extraOptions = [ + "--add-host=host.docker.internal:host-gateway" + "--pull=never" + ]; + dependsOn = ["odysseus-chromadb" "odysseus-searxng" "odysseus-ntfy"]; }; + }; - extraOptions = [ - "--add-host=host.docker.internal:host-gateway" - "--restart=unless-stopped" - ]; + # Restart policy (mkForce overrides oci-containers' default "always") + systemd.services = { + docker-odysseus-chromadb.serviceConfig.Restart = lib.mkForce "on-failure"; + docker-odysseus-searxng.serviceConfig.Restart = lib.mkForce "on-failure"; + docker-odysseus-ntfy.serviceConfig.Restart = lib.mkForce "on-failure"; + docker-odysseus.serviceConfig.Restart = lib.mkForce "on-failure"; }; # ---------------------------- - # Optional: reverse proxy (clean URL) + # Nginx + local DNS # ---------------------------- services.nginx = { enable = true; - virtualHosts."ai.local" = { locations."/" = { proxyPass = "http://127.0.0.1:7000"; proxyWebsockets = true; + extraConfig = '' + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + ''; }; }; }; - networking.firewall.allowedTCPPorts = [ - 7000 - ]; + networking.hosts."127.0.0.1" = ["ai.local"]; + networking.firewall.allowedTCPPorts = [80]; + networking.firewall.interfaces.docker0.allowedTCPPorts = [11434]; } -- cgit v1.2.3