Docs / Architecture
Architecture
How Toru is laid out and what each service does.
Service map
- Web (Next.js 16 / Cloudflare Workers) — marketing pages + dashboard. Realtime-driven via Supabase channels.
- API (Go / chi / pgx) — REST surface at
api.toru.ravengrid.cc. All/v1/*endpoints, JWT + API-key auth. - Stremio addon (Go) — manifest + stream resolver at
addon.toru.ravengrid.cc. - WebDAV (Go) — read-only library mount at
dav.toru.ravengrid.cc. - Poller (Go) — cron loops driving torrent-status advance + RSS auto-grab.
- R2 Worker (Cloudflare Worker, TS) — signed-URL validation + range serving from
media.toru.ravengrid.cc. - Status (Uptime Kuma) — public status page at
status.toru.ravengrid.cc. - Docs (nginx static) — this site, at
docs.toru.ravengrid.cc.
Submit flow
- Client →
POST /v1/torrentswith magnet or URL. - API checks own R2 availability cache → if hit, returns
status=ready. - Otherwise: cache-check across the user's connected debrid backends (StremThru fan-out).
- If user has Torrin connected, submit there before falling through to the shared pool.
- Shared cold-start cascade: Premiumize → Torrin → TorBox → Real-Debrid → AllDebrid (skipping rungs without configured tokens, and skipping shared Torrin if the user already has one). First acceptance wins.
- Job row inserted with
external_id+external_sourceso the poller knows where to ask for status.
Stream resolver layers
- R2 cache —
file.r2_keyset → signed Worker URL (source: "r2"). - External —
file.external_urlset (Torrin / Premiumize folder link / TorBoxrequestdl) → URL returned verbatim, optionally proxy-wrapped (source: "external" | "mediaflow" | "stremthru-proxy"). - User debrid + proxy — StremThru
GenerateLinkForUserwrapped by user's MediaFlow / StremThru proxy if opted in. - Direct upstream — debrid link returned (
source: "upstream").
Encryption
Two AES-256-GCM-encrypted columns, both keyed off the same
TOKEN_ENCRYPTION_KEY env (chmod 600 on the host, never in repo):
user_integrations.token_ciphertext— debrid + proxy bearer tokens.api_keys.key_secret_ciphertext— thetu_secret portion (revealable on demand from the dashboard).
Argon2id hash is also stored on api_keys for fast verify on every authenticated request. Rotation revokes the old key in the same transaction as minting the new one.
RLS posture
Every user-scoped table has Row-Level Security enabled with self-only SELECT/INSERT/UPDATE
policies (user_id = auth.uid()). The Realtime publication on
public.jobs respects these policies, so the dashboard's job channel only
delivers rows the user owns.
Hosting
All Go services run on a single Azure VM behind Traefik (Let's Encrypt-managed certs). Cloudflare DDNS keeps the A/AAAA records in sync. Supabase is reached over IPv6 via a Cloudflare WARP container's network namespace (Supabase's free tier exposes Postgres only over IPv6 and the default Docker bridge is IPv4-only).
Deploy is one command: bash scripts/deploy.sh. It rsyncs the repo, ensures
the parent compose includes Toru's apps, builds + restarts the service containers, and
optionally brings up the status + docs containers based on env config.
Source: github.com/rrevanth/toru.