Per-repository guide
All paths are under /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/ except the
gateway.
| Repository | Role | Stack | Dev / release branch | Port |
|---|---|---|---|---|
gstv-website | Public streaming SPA | React 18, Vite 3, Tailwind | test / main | 5173 |
greyscreen-webview | Mobile streaming SPA | React 18, Vite 5, Vidstack + hls.js | test / main | 5174 |
greyscreen-expo-webview | Phone shell | Expo 54, RN 0.81 | test / main | — |
gstv-tv | Android TV client | Expo 53, react-native-tvos | test / main | — |
gstv-admin-web | Back office SPA | React 18, Vite 5, Ant Design 5 | test / main | 5173 |
gstv-customer-api | Public API | Express 4, TypeScript, SWC | test / main | 3001 dev, 3000 test/prod |
gstv-admin-api | Admin API + orchestration | Express 4, TypeScript, sharp | test / main | 3003 |
gstv-vps | Ephemeral encoder | Node 20, ffmpeg, gcsfuse | test / main | — |
gstv-multimedia | HLS/RTMP server | node-media-server, Express | test / main | 8000 / 8080 / 1935 |
gstv-infra | GCE operator scripts | Bash, gcloud | no git | — |
gstv-secrets | Orchestrator key | JSON | no git | — |
gstv-migration | Migration + indexes + rules | Node, firebase-admin | master | — |
vps-reverse-proxy | Gateway + test stack | nginx, Compose, Bash | one branch per VPS | 80 / 443 |
gstv-website
The public product. Catalogue and trailer pages are anonymous; /play/:id, /home,
/mylist, /liked, /history and /profile are protected.
- Start here:
src/App.tsx,src/lib/{apiClient,endpoints,authToken}.ts,src/components/catalog/CatalogScreen.tsx. - Firebase:
src/Firebase/FirebaseConfig.jsis branch-selected, not env-selected.main→grey-screen-tv;test→greyscreen-test-5fb86. Never merge the test version into production. - Payments: two Stripe implementations exist (hosted flow and embedded flow). Do not add a third; pick the one the screen already uses.
- Gotcha: Vite
build:testvsbuild:prod, plus the Dockerfile'sVITE_MODEargument. A build without the matching.env.${VITE_MODE}refuses to proceed. - Legacy:
src/actions/still uses unauthenticated user-ID endpoints. New code should use the typedapiClient.
greyscreen-webview
Runs inside the phone app. It is not standalone: the native shell supplies the user and token over the bridge, and there is no Firebase auth in the SPA.
- Start here:
src/config/env.ts,src/services/SimpleBridge.ts,src/routes/index.tsx. - Base path: Vite
base: '/webview/', so the dev URL includes the prefix. - Auth: reads
localStorage.userand listens forSEND_USER_DATA_TO_WEBVIEW. It does not validate or refresh the token. - Payments: Stripe embedded checkout and Flutterwave mobile money. Success polls
verifySubcriptionand tells the shell to patch local state. - Gotcha:
.envis a stray copy of production values;.env.testis the test origin. Check which one Vite loads before debugging a host mismatch.
greyscreen-expo-webview
The phone shell: WebView host, Google/Apple sign-in, FCM, Android Cast, TV pairing and remote.
- Start here:
app.config.ts,app/(main)/index.tsx,services/AuthService.ts,services/TVPairingService.ts,services/SimpleBridge.ts. - Builds: EAS.
APP_VARIANT/EAS_BUILD_PROFILEselect the Firebase key folder and app IDs. Android production is built from theandroidbranch; iOS fromios. - Gotcha: signup failure falls back to a random local user in both native clients. It is a demo safeguard, not authentication.
- Security: the repository tracks a production Firebase Admin key and an iOS credential file. Do not reproduce their contents anywhere. See Security notes.
gstv-tv
Android TV lean-back client.
- Start here:
app/_layout.tsx,components/player/TVVideoPlayer.tsx,services/PairingService.ts,services/RemoteEffectBus.ts. - Playback:
utils/streamUrl.tsbuilds<STREAMING_BASE_URL>/{videoId}/{resolution}/playlist.m3u8; trailers are under/stream/trailers. - Payments: none. The TV sends the user to the phone and pairs the account.
- Gotcha: the remote only executes
backandnavigate.screen; other effects are published onRemoteEffectBuswith no subscribers. - Release: EAS + Play;
google-play-key.jsonis referenced but missing.
gstv-admin-web
Back office. JavaScript project, despite some .ts/.tsx files.
- Start here:
src/components/AddVideosModal/index.jsx,src/config/artwork.js,src/actions/upload.js,src/hooks/useNotifications.js. - Wizard order: Video Info → Artwork (two required crops) → Videos (trailer + feature) →
Upload. Upload order is
thumbnail,thumbnailPreview,trailer,video, thensaveVideolast, because the API verifies every object before enqueueing. - Artwork contract:
thumbnailis portrait 3:4,thumbnailPreviewis landscape 16:9; dimensions are mirrored ingstv-admin-api/src/utils/imageArtwork.ts. Change both or neither. - Gotcha: the fallback API host is the legacy
greyscreentv.testapp.pro; ensureVITE_API_BASE_URLis set.
gstv-customer-api
Public API. Read-heavy.
- Start here:
src/server.ts,src/app.ts,src/routes/*,src/db/catalogSnapshot.ts,src/services/catalog/top10.ts,src/controllers/pay.controller.ts. - Auth:
/me*requires a verified Firebase ID token. Legacy/users/*and/signuptrust a client-supplied ID — do not extend them. - Caching: catalogue routes set short revalidation or
no-store; only view aggregates are cached./catalog/browsecan be cached longer because the ID set is rebuilt server-side. - Gotcha: a Firestore write with an
undefinedoptional field throws. Omit absent keys.
gstv-admin-api
Internal API and orchestration plane: uploads, artwork conversion, transcode queue, readiness, VM lifecycle, notifications, deletion.
- Start here:
src/server.ts,src/controllers/uploads.controller.ts,src/services/videos/videoReadiness.service.ts,src/services/vm/vmLifecycle.service.ts,src/pubSub/*,src/routes/notifications.route.ts. - Single instance: the RTDB consumers are claim queues. Exactly one replica per database. The SSE hub alone could scale; the process cannot.
- Uploads: images convert to WebP synchronously in
completeUpload; videos are queued bysaveVideo; the VM is created only while a queue is non-empty. - Gotcha: the legacy
queue/imageJobslane still gates VM creation. A stranded child keeps the VM alive and causes create/delete churn. The producer no longer writes it; do not add one.
gstv-vps
Ephemeral worker. Consumes queue/transcode, runs one job at a time, writes HLS, publishes
resolution events, cleans masters, self-deletes when idle.
- Start here:
src/index.js,src/process/serialProcess.js,src/utils/utils.videos.js,src/status/heartbeat.js,src/lifecycle/shutdown.js. - Ladder: 144p through 2160p; only rungs at or below the source height. Master playlist is written before the renditions.
- Gotcha: the production image is pulled from Artifact Registry, which no repo builds
automatically. CI's
mainworkflow pushes a different registry image and does not deploy. Deploy the worker image before the API when both change.
gstv-multimedia
Long-lived VPS streaming node with a two-tier cache.
- Start here:
index.js,cache-manager/*. - Routes:
/health,/api/cache/*,/videos/*,/trailers/*, catch-all proxy to NMS. - Gotcha: RTMP publish/play and cache stats are unauthenticated; CORS is
*. No DRM.
gstv-infra and gstv-secrets
Operator-only, not CI-deployed.
startup-script.shasserts the environment and starts the worker container.create-instance-template.shrecreates the per-env template.setup-iam.shcreates the VM and orchestrator identities.gstv-secretsholds the high-privilege orchestrator key. Never copy it.
gstv-migration
Reference for Firestore indexes, RTDB rules and the completed Mongo→Firestore import. Its
sync.sh-style workflow for gstv-db-core is not safe: the canonical module has diverged
from the copies shipped in both APIs. Do not run a sync; diff all three first.
vps-reverse-proxy
The public entry point. domains.json is the source of truth; one branch per VPS.
- Test stack:
stacks/greyscreentv-test/docker-compose.ymlis current. Its README is stale about image tags and the Firestore API. - CI: push to a non-main branch → resolve the VPS branch →
git reset --hard→generate.sh→reconcile.sh→ compose up → install crons. - Gotcha: the test stack shares the production Firebase project. The Compose profile keeps the test worker off the shared queue.