Skip to main content

Deployment topology

GreyScreen runs on two hosts — a VPS and Google Cloud — plus four external providers. Nothing runs on a developer's machine except a browser, and the only component that appears and disappears on its own is the transcoding worker.

Map · deployment

Where each piece runs

The gateway is the only public door; the worker is the only ephemeral machine.

Rendering Mermaid diagram…
Zoom 100 percent
Mermaid

The same system, laid out automatically. Use the controls to zoom, open it full screen, or download it for a deck.

The VPS​

One nginx gateway per VPS, one branch per VPS, and one Compose stack underneath.

LayerWhat it is
TLS tierTerminates TLS, one certificate per domain
Domain proxiesOne proxy per domain, longest-prefix match, prefix stripped
ContainersWebsite, webview, admin web, customer API, admin API, multimedia
Config sourcedomains.json, then generate.sh, then reconcile.sh

The test VPS is 82.38.44.60. Production hosts have their own branch of vps-reverse-proxy — the repository is deployed per host, not per environment variable.

A route is not public until the gateway knows it

Adding an endpoint to a repo changes nothing publicly. The route must exist in domains.json and generate.sh must run. This is the most common "it works locally" explanation.

Google Cloud​

Everything stateful lives in one project, grey-screen-mobile-and-web-app:

ServiceRole
Firestore (nam5)Source of truth
Realtime DatabaseQueues, worker status, pairing
Cloud StorageThree buckets per environment: images, input, output
Firebase AuthWebsite and native sign-in
Firebase Cloud MessagingPush tokens stored on the user document
Compute EngineThe on-demand worker VM
Artifact RegistryThe worker image the instance template pulls

The worker is the only machine that is created on demand. It is not a managed service, not a cluster, and not autoscaled: a scheduler in the admin API creates or deletes one fixed-name instance, gstv-vps-<env>-worker, based on whether a queue is non-empty.

Two orchestrators, one project​

Test and production each have an admin API that can create worker VMs. They are safe together only because:

  • each VM carries a labels.env of test or prod;
  • each scheduler filters on its own label and re-checks client-side before acting.

Never remove either guard. Two workers on one queue would double-process every job.

Boot and shutdown of the worker​

  1. The scheduler sees a non-empty queue/transcode (or the legacy queue/imageJobs).
  2. It inserts an instance from the per-environment template.
  3. startup-script.sh asserts metadata, env identity, bucket names, the baked Firebase key, the RTDB host and the timers.
  4. It pulls the image, mounts input and output with gcsfuse, starts the container.
  5. The worker drains the queue, then deletes its own VM after five idle minutes, or at the 24-hour hard cap regardless of work.

A vpsStatus/heartbeat record naming a VM that no longer exists is normal. It ages out at VPS_HEARTBEAT_STALE_MS (default 60 s) and must not be read as "a VM is running".

Images and drift​

The worker has three image references in the wild:

ReferenceBuilt by
docker.aigenhub.us/gstv-vps-test:testCI on test
docker.aigenhub.us/vps:prodCI on main, no deploy job
us-central1-docker.pkg.dev/.../gstv-vps:<env>The instance template pulls this; no repo builds it

This is the single most confusing part of the platform. The test path is coherent. The production path is not: the image the VM actually pulls is not the image CI pushes.

Secret Manager secrets exist but the boot script never fetches them; it validates an image-baked Firebase key and uses application default credentials for gcsfuse.

Native clients​

The phone shell and TV app ship through EAS, not through the VPS.

ClientDistributionFirebase identity
Phone shellEAS profiles; Android from android, iOS from iosSelected by build variant in app.config.ts
Android TVEAS + PlaySame variant pattern; google-play-key.json is referenced but missing

There is no Docker or server deploy for either. A native release is independent of a VPS deploy, which is why a client bug can outlive a backend rollback.

Public path layout​

PathUpstream
/website
/webview/webview SPA
/admin/admin SPA
/api/customer/customer API
/api/admin/admin API
/stream/multimedia

Everything else on a host is routed by domains.json.

What "deploy" means per repository​

RepositoryTestProduction
Website, webview, admin web, both APIs:test image, SSH + flock:prod image, GitHub production environment
WorkerCompose vps profile, usually offManual; no deploy job
MultimediaAuto-deploys on test with no gateSeparate path
Native appsEAS previewEAS production + store
GatewayPush to a non-main branchPush to the VPS branch

Deploy order: if a change touches both the worker and the API, deploy the worker image first.

Next: Build and deploy.