# LibNovel homelab runner # # Connects to production PocketBase and MinIO via public subdomains. # All secrets come from Doppler (project=libnovel, config=prd_homelab). # Run with: doppler run -- docker compose up -d # # Differs from prod runner: # - RUNNER_WORKER_ID=homelab-runner-1 (unique, avoids task claiming conflicts) # - MINIO_ENDPOINT/USE_SSL → storage.libnovel.cc over HTTPS # - POCKETBASE_URL → https://pb.libnovel.cc # - MEILI_URL → https://search.libnovel.cc (Caddy-proxied) # - VALKEY_ADDR → unset (not exposed publicly) # - RUNNER_SKIP_INITIAL_CATALOGUE_REFRESH=true # - Redis service for Asynq task queue (local to homelab, exposed to prod via Caddy TCP proxy) # - LibreTranslate service for machine translation (internal network only) services: redis: image: redis:7-alpine restart: unless-stopped volumes: - redis_data:/data command: > redis-server --appendonly yes --requirepass "${REDIS_PASSWORD}" healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 libretranslate: image: libretranslate/libretranslate:latest restart: unless-stopped environment: LT_API_KEYS: "true" LT_API_KEYS_DB_PATH: "/app/db/api_keys.db" # Limit to source→target pairs the runner actually uses LT_LOAD_ONLY: "en,ru,id,pt,fr" LT_DISABLE_WEB_UI: "true" LT_UPDATE_MODELS: "false" volumes: - libretranslate_models:/home/libretranslate/.local/share/argos-translate - libretranslate_db:/app/db healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:5000/languages || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 120s runner: image: kalekber/libnovel-runner:latest restart: unless-stopped stop_grace_period: 135s depends_on: redis: condition: service_healthy libretranslate: condition: service_healthy environment: # ── PocketBase ────────────────────────────────────────────────────────── POCKETBASE_URL: "https://pb.libnovel.cc" POCKETBASE_ADMIN_EMAIL: "${POCKETBASE_ADMIN_EMAIL}" POCKETBASE_ADMIN_PASSWORD: "${POCKETBASE_ADMIN_PASSWORD}" # ── MinIO (S3 API via public subdomain) ───────────────────────────────── MINIO_ENDPOINT: "storage.libnovel.cc" MINIO_ACCESS_KEY: "${MINIO_ROOT_USER}" MINIO_SECRET_KEY: "${MINIO_ROOT_PASSWORD}" MINIO_USE_SSL: "true" MINIO_PUBLIC_ENDPOINT: "${MINIO_PUBLIC_ENDPOINT}" MINIO_PUBLIC_USE_SSL: "${MINIO_PUBLIC_USE_SSL}" # ── Meilisearch (via search.libnovel.cc Caddy proxy) ──────────────────── MEILI_URL: "${MEILI_URL}" MEILI_API_KEY: "${MEILI_API_KEY}" VALKEY_ADDR: "" # Force IPv4 DNS resolution — homelab has no IPv6 route to search.libnovel.cc GODEBUG: "preferIPv4=1" # ── Kokoro TTS ────────────────────────────────────────────────────────── KOKORO_URL: "${KOKORO_URL}" KOKORO_VOICE: "${KOKORO_VOICE}" # ── Pocket TTS ────────────────────────────────────────────────────────── POCKET_TTS_URL: "${POCKET_TTS_URL}" # ── LibreTranslate (internal Docker network) ──────────────────────────── LIBRETRANSLATE_URL: "http://libretranslate:5000" LIBRETRANSLATE_API_KEY: "${LIBRETRANSLATE_API_KEY}" # ── Asynq / Redis (local service) ─────────────────────────────────────── # The runner connects to the local Redis sidecar. REDIS_ADDR: "redis:6379" REDIS_PASSWORD: "${REDIS_PASSWORD}" # ── Runner tuning ─────────────────────────────────────────────────────── RUNNER_WORKER_ID: "${RUNNER_WORKER_ID}" RUNNER_POLL_INTERVAL: "${RUNNER_POLL_INTERVAL}" RUNNER_MAX_CONCURRENT_SCRAPE: "${RUNNER_MAX_CONCURRENT_SCRAPE}" RUNNER_MAX_CONCURRENT_AUDIO: "${RUNNER_MAX_CONCURRENT_AUDIO}" RUNNER_MAX_CONCURRENT_TRANSLATION: "${RUNNER_MAX_CONCURRENT_TRANSLATION}" RUNNER_TIMEOUT: "${RUNNER_TIMEOUT}" RUNNER_METRICS_ADDR: "${RUNNER_METRICS_ADDR}" RUNNER_SKIP_INITIAL_CATALOGUE_REFRESH: "true" # ── Observability ─────────────────────────────────────────────────────── LOG_LEVEL: "${LOG_LEVEL}" GLITCHTIP_DSN: "${GLITCHTIP_DSN}" healthcheck: test: ["CMD", "/healthcheck", "file", "/tmp/runner.alive", "120"] interval: 60s timeout: 5s retries: 3 volumes: redis_data: libretranslate_models: libretranslate_db: