Architecture
GreyScreen is a set of small services behind one gateway. The gateway owns TLS and path routing; the services own one job each; Firebase owns shared state. Two flows are easy to miss: artwork is converted inside the admin API, and the admin learns the outcome of an encode over SSE, not by polling.
Switch between Explained and Technical below. Both maps are live: hover to isolate connections, click a node for its role and key files, trace a flow, zoom, open full screen, and download the map as SVG or PNG.
The platform, service by service
Every runtime component, the protocol between them, and the flows that matter. Click a node for its role, stack and key files.
The shape in five decisions
1. One public surface
vps-reverse-proxy is a two-tier nginx gateway: a TLS gateway on :80/:443, and one proxy
per domain that matches the longest path prefix and strips it before forwarding. The
upstream container therefore sees /catalog/home, not /api/customer/catalog/home.
| Public path | Upstream | Prefix stripped |
|---|---|---|
/ | gstv-website | no |
/webview/ | greyscreen-webview | yes |
/admin/ | gstv-admin-web | yes |
/api/customer/ | gstv-customer-api:3000 | yes |
/api/admin/ | gstv-admin-api:3003 | yes |
/stream/ | gstv-multimedia:8000 | yes |
2. Static where possible
The website, webview and admin SPA are static nginx images with an SPA fallback and /healthz.
They hold no server secrets. The phone shell and TV app are built with EAS and are not part of
the Docker pipeline.
3. Two APIs, one database
gstv-customer-api is read-heavy and public. gstv-admin-api is internal and orchestration-heavy.
They share one Firestore database, so a schema change in either is a contract change for the
other API and for every client. Firestore document IDs are API contracts: _id is synthesised
from the document ID, and plans and admins keep their legacy Mongo _id strings.
gstv-admin-api must run as exactly one replica because its RTDB consumers are
child_added claim queues with no lease. The notification hub is the exception — it is a
fan-out log — but the process cannot scale until the claim consumers get a lease.
4. The catalogue is a live snapshot
gstv-customer-api subscribes to videos, series and seasons where isReady == true and
keeps the result in memory. It re-subscribes with backoff. Readiness is set by a Firestore
transaction in the admin API only after the transcode is complete, the trailer has a
resolution, and the artwork exists. There is no cache to invalidate; the snapshot just changes.
Only aggregate view counts are cached (Top 10, five minutes). The ranked board is rebuilt from the live catalogue on every request, so a deleted or unpublished title cannot linger.
5. Uploads bypass the API
Browsers upload directly to Cloud Storage using signed PUTs or resumable session URIs. The API
signs, verifies and records; it does not proxy bytes. Artwork is converted to WebP synchronously
during completeUpload, so the response already describes the final image.
The same system in Mermaid
Mermaid produces a second, automatically laid-out reading from the same component model. It is useful for a slide, a review, or a quick sanity check that the service list is still complete.
Auto-laid out with Mermaid, rendered live in the page.
The same story, drawn by Mermaid
A second view of the platform for readers who prefer a simpler, box-and-arrow map. Every block opens a plain-language card.
The same system, laid out automatically. Use the controls to zoom, open it full screen, or download it for a deck.
How the pieces connect
| Edge | Protocol | Contract |
|---|---|---|
| Browser → gateway | HTTPS | Host + path; TLS terminated at the gateway |
| Gateway → SPAs | HTTP | Prefix stripped; SPA fallback; /healthz |
| Gateway → APIs | HTTP | JSON REST; /health; Bearer token or admin cookie |
| Gateway → multimedia | HTTP | HLS playlists and segments under /stream/ |
| Customer API → Firestore | Admin SDK | Live onSnapshot on ready catalogue docs |
| Admin API → Firestore | Admin SDK | CRUD, staging, readiness transaction, notifications |
| Admin API ↔ worker | RTDB | queue/transcode, queue/resolutionEvents, vpsStatus |
| Worker → buckets | gcsfuse | Read masters from input, write HLS to output |
| Multimedia → output | gcsfuse | Read HLS; cache in RAM then disk |
| Stripe / Flutterwave → customer API | HTTPS webhook | Signature or verif-hash; dedupe where implemented |
| Admin API → admin SPA | SSE | /notifications/stream, cookie auth, Last-Event-ID replay |
Reading paths
- New to the product: How GreyScreen works.
- Changing a request path: Request lifecycle.
- Changing uploads: Upload to publish, or watch the animated ingest journey end to end.
- Changing playback: Playback.
- Deploying: Deployment topology and Build and deploy.
- Words you keep seeing: Glossary.