aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hosts/herra/ai.nix165
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];
}