diff --git a/ai/compose.yaml b/ai/compose.yaml new file mode 100644 index 0000000..5c15f17 --- /dev/null +++ b/ai/compose.yaml @@ -0,0 +1,119 @@ +services: + # PostgreSQL with pgvector for AI/RAG workloads + postgres: + image: ${AI_POSTGRES_IMAGE:-pgvector/pgvector:pg16} + container_name: ${AI_COMPOSE_PROJECT_NAME}_postgres + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + POSTGRES_USER: ${AI_DB_USER} + POSTGRES_PASSWORD: ${AI_DB_PASSWORD} + POSTGRES_DB: ${AI_DB_NAME} + POSTGRES_HOST_AUTH_METHOD: scram-sha-256 + POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256 + volumes: + - ai_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${AI_DB_USER}"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - compose_network + + # Open WebUI - ChatGPT-like interface for AI models + webui: + image: ${AI_WEBUI_IMAGE:-ghcr.io/open-webui/open-webui:main} + container_name: ${AI_COMPOSE_PROJECT_NAME}_webui + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + + # Database configuration + DATABASE_URL: postgresql://${AI_DB_USER}:${AI_DB_PASSWORD}@postgres:5432/${AI_DB_NAME} + + # OpenAI API configuration (for Claude via Anthropic API) + OPENAI_API_BASE_URLS: ${AI_OPENAI_API_BASE_URLS:-https://api.anthropic.com/v1} + OPENAI_API_KEYS: ${ANTHROPIC_API_KEY} + + # WebUI configuration + WEBUI_NAME: ${AI_WEBUI_NAME:-Pivoine AI} + WEBUI_URL: https://${AI_TRAEFIK_HOST} + WEBUI_SECRET_KEY: ${AI_WEBUI_SECRET_KEY} + + # Feature flags + ENABLE_SIGNUP: ${AI_ENABLE_SIGNUP:-true} + ENABLE_RAG_WEB_SEARCH: ${AI_ENABLE_RAG_WEB_SEARCH:-true} + ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION: ${AI_ENABLE_RAG_SSL_VERIFY:-true} + + # RAG configuration + RAG_EMBEDDING_ENGINE: ${AI_RAG_EMBEDDING_ENGINE:-openai} + RAG_EMBEDDING_MODEL: ${AI_RAG_EMBEDDING_MODEL:-text-embedding-3-small} + VECTOR_DB: ${AI_VECTOR_DB:-pgvector} + + # Email configuration (IONOS SMTP) + SMTP_HOST: ${EMAIL_SMTP_HOST} + SMTP_PORT: ${EMAIL_SMTP_PORT} + SMTP_USER: ${EMAIL_SMTP_USER} + SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD} + SMTP_FROM_EMAIL: ${EMAIL_FROM} + SMTP_USE_TLS: false + SMTP_USE_SSL: true + + volumes: + - ai_webui_data:/app/backend/data + depends_on: + - postgres + networks: + - compose_network + labels: + - 'traefik.enable=${AI_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect + - 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.rule=Host(`${AI_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.entrypoints=web' + # HTTPS router + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${AI_TRAEFIK_HOST}`)' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' + - 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' + - 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file' + # Service + - 'traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=8080' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + # Crawl4AI - Web scraping for LLMs (internal API, no public access) + crawl4ai: + image: ${AI_CRAWL4AI_IMAGE:-unclecode/crawl4ai:latest} + container_name: ${AI_COMPOSE_PROJECT_NAME}_crawl4ai + restart: unless-stopped + environment: + TZ: ${TIMEZONE:-Europe/Berlin} + # API configuration + PORT: ${AI_CRAWL4AI_PORT:-11235} + volumes: + - ai_crawl4ai_data:/app/.crawl4ai + networks: + - compose_network + labels: + # No Traefik exposure - internal only + - 'traefik.enable=false' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + +volumes: + ai_postgres_data: + name: ${AI_COMPOSE_PROJECT_NAME}_postgres_data + ai_webui_data: + name: ${AI_COMPOSE_PROJECT_NAME}_webui_data + ai_crawl4ai_data: + name: ${AI_COMPOSE_PROJECT_NAME}_crawl4ai_data + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/arty.yml b/arty.yml index 2c3fffb..fd38433 100644 --- a/arty.yml +++ b/arty.yml @@ -162,6 +162,24 @@ envs: # Sablier SABLIER_COMPOSE_PROJECT_NAME: sablier SABLIER_VERSION: latest + # AI Stack + AI_TRAEFIK_ENABLED: true + AI_COMPOSE_PROJECT_NAME: ai + AI_POSTGRES_IMAGE: pgvector/pgvector:pg16 + AI_WEBUI_IMAGE: ghcr.io/open-webui/open-webui:main + AI_CRAWL4AI_IMAGE: unclecode/crawl4ai:latest + AI_TRAEFIK_HOST: ai.pivoine.art + AI_DB_USER: ai + AI_DB_NAME: openwebui + AI_WEBUI_NAME: Pivoine AI + AI_ENABLE_SIGNUP: true + AI_ENABLE_RAG_WEB_SEARCH: true + AI_ENABLE_RAG_SSL_VERIFY: true + AI_RAG_EMBEDDING_ENGINE: openai + AI_RAG_EMBEDDING_MODEL: text-embedding-3-small + AI_VECTOR_DB: pgvector + AI_CRAWL4AI_PORT: 11235 + AI_OPENAI_API_BASE_URLS: https://api.anthropic.com/v1 # Watchtower WATCHTOWER_POLL_INTERVAL: 300 WATCHTOWER_LABEL_ENABLE: true diff --git a/compose.yaml b/compose.yaml index b649bb7..530cd46 100644 --- a/compose.yaml +++ b/compose.yaml @@ -14,6 +14,7 @@ include: - kit/compose.yaml - jelly/compose.yaml - drop/compose.yaml + - ai/compose.yaml - restic/compose.yaml - netdata/compose.yaml - umami/compose.yaml diff --git a/restic/compose.yaml b/restic/compose.yaml index 4103f8c..7c93362 100644 --- a/restic/compose.yaml +++ b/restic/compose.yaml @@ -36,6 +36,9 @@ services: - backup_joplin_data:/volumes/joplin_data:ro - backup_jelly_config:/volumes/jelly_config:ro - backup_netdata_config:/volumes/netdata_config:ro + - backup_ai_postgres_data:/volumes/ai_postgres_data:ro + - backup_ai_webui_data:/volumes/ai_webui_data:ro + - backup_ai_crawl4ai_data:/volumes/ai_crawl4ai_data:ro environment: TZ: ${TIMEZONE:-Europe/Berlin} @@ -138,6 +141,15 @@ volumes: backup_netdata_config: name: netdata_config external: true + backup_ai_postgres_data: + name: ai_postgres_data + external: true + backup_ai_webui_data: + name: ai_webui_data + external: true + backup_ai_crawl4ai_data: + name: ai_crawl4ai_data + external: true networks: compose_network: diff --git a/restic/config.json b/restic/config.json index a6b4ee6..ea7b774 100644 --- a/restic/config.json +++ b/restic/config.json @@ -280,6 +280,26 @@ "monthly": 3 } } + }, + { + "id": "ai-backup", + "repo": "hidrive-backup", + "paths": [ + "/volumes/ai_postgres_data", + "/volumes/ai_webui_data", + "/volumes/ai_crawl4ai_data" + ], + "schedule": { + "cron": "0 3 * * *" + }, + "retention": { + "policyTimeBucketed": { + "daily": 7, + "weekly": 4, + "monthly": 6, + "yearly": 2 + } + } } ] }