# 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_ADDR → rediss://redis.libnovel.cc:6380 (prod Redis via Caddy TLS proxy) # - LibreTranslate service for machine translation (internal network only) services: 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", "python3", "-c", "import urllib.request,sys; urllib.request.urlopen('http://localhost:5000/languages'); sys.exit(0)"] interval: 30s timeout: 10s retries: 5 start_period: 120s runner: image: kalekber/libnovel-runner:latest restart: unless-stopped stop_grace_period: 135s depends_on: 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 (prod Redis via Caddy TLS proxy) ────────────────────── # The runner connects to prod Redis over TLS: rediss://redis.libnovel.cc:6380. # Caddy on prod terminates TLS and proxies to the local redis:6379 sidecar. REDIS_ADDR: "${REDIS_ADDR}" 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: libretranslate_models: libretranslate_db: