Compare commits

...

5 Commits

Author SHA1 Message Date
Admin
7a4008bd9c chore: improve workflow job names for clarity
All checks were successful
Release / Test backend (push) Successful in 1m2s
Release / Test UI (push) Successful in 58s
Release / Build and push images (push) Successful in 4m34s
Release / Deploy to prod (push) Successful in 2m24s
Release / Deploy to homelab (push) Successful in 15s
Release / Gitea Release (push) Successful in 20s
- 'Check ui' → 'Test UI' (consistent with 'Test backend')
- 'Docker' → 'Build and push images' (more descriptive of what it does)

Job IDs remain unchanged (test-backend, check-ui, docker) for stability.
2026-04-16 21:34:23 +05:00
Admin
f4834f968a fix: disable strict host key checking for homelab SSH
Some checks failed
Release / Test backend (push) Successful in 55s
Release / Check ui (push) Successful in 1m0s
Release / Docker (push) Failing after 2m52s
Release / Deploy to prod (push) Has been skipped
Release / Deploy to homelab (push) Has been skipped
Release / Gitea Release (push) Has been skipped
Homelab is on private network (192.168.0.109), so we can safely disable
strict host key checking. This avoids the complexity of managing known_hosts
entries in Gitea secrets.

Changes:
- Remove HOMELAB_SSH_KNOWN_HOSTS requirement
- Add -o StrictHostKeyChecking=no to scp/ssh commands
- Add -o UserKnownHostsFile=/dev/null to avoid host key persistence
2026-04-16 21:23:59 +05:00
Admin
32ee3c302d chore: add .opencode/ to gitignore
Local OpenCode agent state (memory, node_modules) shouldn't be committed.
2026-04-16 20:34:05 +05:00
Admin
f5650a98ec chore: remove unused homelab/runner directory
We use homelab/docker-compose.yml (full stack) for the homelab deployment,
not homelab/runner/docker-compose.yml (runner-only subset). Removing the
unused directory to prevent confusion.
2026-04-16 20:25:37 +05:00
Admin
9c3b235382 fix: copy full homelab compose file, not runner-only subset
Some checks failed
Release / Test backend (push) Successful in 1m1s
Release / Check ui (push) Successful in 1m2s
Release / Docker (push) Successful in 9m22s
Release / Deploy to prod (push) Successful in 2m32s
Release / Gitea Release (push) Successful in 1m37s
Release / Deploy to homelab (push) Failing after 5s
CRITICAL FIX: The homelab server runs the full stack (runner + GlitchTip +
observability tools), not just the runner. Copying homelab/runner/docker-compose.yml
would have destroyed all other services.

Changed: homelab/runner/docker-compose.yml → homelab/docker-compose.yml
2026-04-16 20:22:10 +05:00
3 changed files with 8 additions and 119 deletions

View File

@@ -32,7 +32,7 @@ jobs:
# ── ui: type-check & build ────────────────────────────────────────────────────
check-ui:
name: Check ui
name: Test UI
runs-on: ubuntu-latest
defaults:
run:
@@ -57,7 +57,7 @@ jobs:
# ── docker: build + push all images via docker bake ──────────────────────────
docker:
name: Docker
name: Build and push images
runs-on: ubuntu-latest
needs: [test-backend, check-ui]
steps:
@@ -152,17 +152,20 @@ jobs:
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.HOMELAB_SSH_KEY }}" > ~/.ssh/homelab_key
chmod 600 ~/.ssh/homelab_key
printf '%s\n' "${{ secrets.HOMELAB_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
- name: Copy docker-compose.yml to homelab
run: |
scp -i ~/.ssh/homelab_key \
homelab/runner/docker-compose.yml \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
homelab/docker-compose.yml \
"${{ secrets.HOMELAB_USER }}@${{ secrets.HOMELAB_HOST }}:/opt/libnovel-runner/docker-compose.yml"
- name: Pull new runner image and restart
run: |
ssh -i ~/.ssh/homelab_key \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
"${{ secrets.HOMELAB_USER }}@${{ secrets.HOMELAB_HOST }}" \
'set -euo pipefail
cd /opt/libnovel-runner

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ Thumbs.db
*.swp
*.swo
*~
.opencode/

View File

@@ -1,115 +0,0 @@
# 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)
#
# extra_hosts pins storage.libnovel.cc and pb.libnovel.cc to the prod server IP
# (165.22.70.138) so that large PutObject uploads and PocketBase writes bypass
# Cloudflare's 100-second proxy timeout entirely. TLS still terminates at Caddy
# on prod; the TLS certificate is valid for the domain names so SNI works fine.
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
runner:
image: kalekber/libnovel-runner:latest
restart: unless-stopped
stop_grace_period: 135s
labels:
- "com.centurylinklabs.watchtower.enable=true"
depends_on:
- libretranslate
# Pin prod subdomains to the prod server IP to bypass Cloudflare's 100s
# proxy timeout. Large MP3 PutObject uploads and PocketBase writes go
# directly to Caddy on prod; TLS and SNI still work normally.
extra_hosts:
- "storage.libnovel.cc:165.22.70.138"
- "pb.libnovel.cc:165.22.70.138"
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}"
# ── Cloudflare Workers AI TTS ────────────────────────────────────────────
CFAI_ACCOUNT_ID: "${CFAI_ACCOUNT_ID}"
CFAI_API_TOKEN: "${CFAI_API_TOKEN}"
# ── 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_RUNNER}"
healthcheck:
test: ["CMD", "/healthcheck", "file", "/tmp/runner.alive", "120"]
interval: 60s
timeout: 5s
retries: 3
volumes:
libretranslate_models:
libretranslate_db: