The 'external drive' turned out to be a CIFS-mounted Hetzner storage
box — SQLite cannot run on a network mount (WAL shared memory and
locking break), and the live DB is only ~7MB anyway. So: the hot
database stays on the local volume, and a nightly VACUUM INTO snapshot
(transactionally consistent, a plain sequential file write — perfect
for CIFS) lands in /data/archive, which compose binds to the storage
box via HOST_ARCHIVE_DIR. 14-day retention, plus a snapshot at boot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HOST_DATA_DIR in .env can now point /data at a host path (the 1TB
external drive) instead of the named volume, so odds history, caches
and snapshots stop eating the 128GB root disk. A daily prune drops
cache entries stale for over a week and ingest-log rows older than 14
days — faster polling no longer means unbounded DB growth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Persistence via Node's built-in node:sqlite (zero native deps) on a Docker
volume: response cache, source health, fixture↔provider id map, per-fixture
enrichment, ingest log. Runtime bumped to node:24-slim + --experimental-sqlite.
- Resilient fetcher: DB cache + per-source rate-limit + jittered backoff +
circuit-breaker (blocked sources trip + skip; UI reads DB, never breaks).
- ESPN hidden API as the PRIMARY rich source (works from the VPS where SofaScore
403s): scoreboard (live scores w/ clock) + summary (venue, H2H, recent form,
lineups, team stats). football-data / SofaScore are fallbacks.
- Scheduler: maps all 72 group fixtures to ESPN event ids, polls live on a
dynamic cadence, enriches imminent fixtures into match_ext. /api/sources/health.
- Verified: DB populates (real H2H + form for opening matches), 22 tests pass,
Docker image runs node:sqlite on the volume and persists across restart.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>