diff options
Diffstat (limited to 'hosts/herra/ai.nix')
| -rw-r--r-- | hosts/herra/ai.nix | 165 |
1 files changed, 140 insertions, 25 deletions
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]; } |
