{ pkgs, lib, ... }: let puid = "1000"; pgid = "100"; odysseusRepo = "https://github.com/pewdiepie-archdaemon/odysseus.git"; odysseusDir = "/var/lib/odysseus/src"; in { # ---------------------------- # Ollama (native) # ---------------------------- services.ollama = { enable = true; 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/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 -" ]; # ---------------------------- # 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-chromadb = { image = "chromadb/chroma:latest"; ports = ["127.0.0.1:8100:8000"]; volumes = ["/var/lib/odysseus/chromadb:/chroma/chroma"]; environment.ANONYMIZED_TELEMETRY = "FALSE"; }; 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/"; }; 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"; }; 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"]; }; }; # 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"; }; # ---------------------------- # 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.hosts."127.0.0.1" = ["ai.local"]; networking.firewall.allowedTCPPorts = [80]; networking.firewall.interfaces.docker0.allowedTCPPorts = [11434]; }