Architecture overview¶
Vistilla is deliberately small. Everything the server does lives in one Rust
binary, vistilla-api. It serves:
- the web pages and their static assets;
- the JSON API;
- video streaming and downloads;
- this documentation;
- and, in the same process, the background workers.
State lives in two places:
- a SQLite database for every record (users, videos, comments…);
- an object store for the big files (recordings, poster images). This is either a folder on disk or any S3-compatible bucket.
+----------------------------------------------------------------+
| Browser |
| HTML pages + ES modules (no framework, no build step) |
| getDisplayMedia / getUserMedia -> canvas -> MediaRecorder |
+----------------------------------------------------------------+
| HTTPS, session cookie vistilla_session
v
+----------------------------------------------------------------+
| Cloudflare Worker: reverse proxy, 'starting' page, |
| cron ping GET /api/healthz every 10 minutes |
+----------------------------------------------------------------+
|
v
+----------------------------------------------------------------+
| vistilla-api (axum on tokio) |
| pages / /app /record /v/:id /s/:token /settings ... |
| static /assets/* /docs/* |
| API /api/* JSON; errors as {"error": ...} |
| media /media/* proxied with Range, or 307 to presigned |
| jobs seek index > silences > transcript > summary > |
| sweep > backfill |
+----------------------------------------------------------------+
| sqlx | SigV4 over HTTPS | HTTPS
v v v
+------------------+ +--------------------+ +--------------------+
| SQLite (WAL) | | Object store | | External services |
| /data/vistilla.db | | fs, or S3: B2, R2, | | AI providers |
| replicated by | | MinIO, AWS | | mail relay |
| Litestream to | | videos/ thumbs/ | | |
| the bucket | | litestream/ | | |
+------------------+ +--------------------+ +--------------------+
The AI providers are Google Gemini, OpenAI and Alibaba Qwen (chosen per workspace), plus Anthropic for summaries when the operator sets it as the server-wide fallback. Mail goes out through the first configured of SMTP, Brevo's API or the Apps Script relay.
Request flow¶
- A page such as
/appis a static HTML file. The server only injects link-preview tags into/v/:idand/s/:token. - The page's JavaScript calls the JSON API under
/apiwith the session cookie. - The API checks the session, works out the caller's role, and reads or writes SQLite.
- The video element streams from
/media/.... The server checks access, then either streams the bytes itself (with HTTP Range support) or redirects to a short-lived signed URL for the bucket. On the hosted instance that URL points at the Cloudflare Worker's/_b2/, which passes it on to Backblaze (VISTILLA_S3_DOWNLOAD_PROXY), because what leaves Backblaze for Cloudflare is free.
Why it is shaped this way¶
- One process, no queue service. Background work (transcripts,
summaries) is stored as rows in SQLite and picked up by
workers in the same process. A replica can turn its workers off with
VISTILLA_RUN_WORKERS=0. - No front-end build. Pages and modules are served as written. Asset
URLs never change, so every asset is sent with
Cache-Control: no-cacheand revalidated (a cheap 304) rather than going stale. - No AWS SDK. S3 access is a small hand-written SigV4 signer, which keeps the binary and the build small.
- Free hosting. The hosted instance runs on a free Render web service with no disk. A new server takes the database over from the running one (or, with none running, Litestream restores it from the bucket) and Litestream streams every change back. See Deployment.
The parts¶
| Part | Page |
|---|---|
| Rust server: modules, startup, workers, middleware | Backend |
| Pages, modules, recording in the browser, theming | Frontend |
| Tables and migrations | Database |
| Uploads, seek index, trimming, silences, streaming | Media pipeline |
| Transcription, summaries, chapters, captions | AI pipeline |
| In-app notifications, email | Notifications and email |
| Sessions, roles, encryption, headers | Security |