Eranaut

A voyage-timer PWA for an FFXIV player community

Eranaut lets players record when their submarines return and reminds them by Discord DM, channel mention or browser push. It is used internally by a community of about 100 people. I built everything on my own, from the screens and API to screenshot recognition and deployment, in collaboration with AI: I own the requirements, design decisions and on-device verification, and every decision is recorded in a decision log.

Architecture

Eranaut system architectureThe browser reaches the Eranaut API public port through Cloudflare Tunnel and Caddy; the Eranarch bot uses the internal port only; both entry points share one service and repository layer on top of SQLite, OCR and two schedulers; the schedulers send reminders through the Discord API and a Web Push service.Self-hosted · Docker ComposeBrowser / PWAAngulareranaut-tunnelcloudflarederanaut-webCaddystatic + /api/*eranarch-botdiscord.js/eran commandseranaut-api · FastifyPublic portsession cookieInternal portshared-secret BearerService / repo layerBusiness rules and SQLlive only hereSQLiteOCRPP-OCRv4in-process2 schedulersreminder polleligibility checkDiscord APIWeb Push serviceHTTPSDiscord gatewayWeb PushDM · channel mention
The host opens no inbound port; traffic comes in only through Cloudflare Tunnel. The bot and the API share a Docker network and call each other by service name. Dashed boxes are external services.

Screens

Eranaut desktop overview showing submarines sorted by return time (demo data)
Eranaut mobile overview (demo data)

Screenshots use the demo’s fake data.

Features

  • Discord OAuth2 login with role-based access (rules combine with AND / OR)
  • Screenshot recognition with a self-hosted PP-OCRv4: no external service, images are not stored, paste and screen-capture supported
  • Three reminder channels: Discord DM, channel mention, browser push (Web Push)
  • PWA: mobile and desktop layouts, adjustable interface size, offline app shell
  • Admin and member commands (via the Eranarch bot calling an internal API)

Tech stack

  • Angular · TypeScript
  • Fastify · Node.js
  • SQLite (better-sqlite3)
  • PP-OCRv4 (onnxruntime-node)
  • Web Push
  • Docker
  • Cloudflare Tunnel + Caddy

Design trade-offs

  • Server-side session cookies instead of JWT: a single API with SQLite means sessions can be revoked instantly.
  • A public port and an internal port sharing one service layer: the bot can only use the internal port, which the internet cannot reach by construction.
  • OCR runs in the same process, with no external service and no stored images, which keeps privacy and cost under control.
  • Reminders use upsert and the whole system has just two schedulers, so data stays bounded and needs no cleanup job.

The hardest parts: OCR and Docker deployment

I validated OCR against 11 real screenshots plus 77 degraded variants (about 99.5% per-submarine accuracy) and ran the full test suite on both Windows x64 and Linux arm64. On arm64, Docker builds hit native-module problems (better-sqlite3, onnxruntime-node); I worked through them one by one and went live on my own machine.