Running locally
There are three levels of local setup. Pick the smallest one that answers your question.
| Level | What runs locally | Secrets needed | Good for |
|---|---|---|---|
| 1. Frontend only | One SPA | none | UI, copy, layout, player chrome |
| 2. One API + SPA | SPA + customer or admin API | non-production Firebase key, .env.*.local | API and catalogue work |
| 3. Full stack | Both APIs, multimedia, worker | Firebase, GCS, Docker, gcsfuse | uploads, transcoding, streaming |
Most changes do not need level 3.
Level 1 — a frontend against the test environment
The deployed test origin is https://test.greyscreentv.com. It already serves the catalogue,
the APIs and the streaming path. No secret is required.
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-website
npm ci
npm run dev # http://localhost:5173
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/greyscreen-webview
npm ci
npm run dev # http://localhost:5174/webview/
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-admin-web
npm ci
npm run dev # http://localhost:5173/admin/
The webview and admin dev servers keep their repository-specific behaviour:
greyscreen-webviewusesbase: '/webview/'and port5174, so its URLs include the prefix.gstv-admin-web's Vite dev server proxies/api/admintohttps://test.greyscreentv.comby default. If you need the local admin API instead, change the proxy target in your working copy, not in a commit.
gstv-admin-web/src/config/index.js falls back to the legacy host
https://greyscreentv.testapp.pro/api/admin when VITE_API_BASE_URL is absent. A build without
its env file will silently talk to a deployment that is no longer current. Check the Network
tab before debugging anything else.
Level 2 — an API on your machine
1. Get the env layers
Both APIs expect:
.env.template— tracked, names only. Use it as the checklist..env.nonprod.local— shared dev/test values, not tracked..env.development.local— dev overlay, not tracked.
The admin API additionally requires FIREBASE_DATABASE_URL (RTDB) because it consumes queues.
The customer API does not need RTDB for most routes, though it reads it in a few places.
The service-account path is provided by FIREBASE_SERVICE_ACCOUNT. Do not rename the key file to
something that implies an environment it is not; validateEnv() checks the project inside it.
2. Start the customer API
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-customer-api
npm ci
APP_ENV=dev npm run dev # port 3001
Expected boot order: validateEnv() → connectAllDatabases() → startCatalogSnapshot() →
routes → listen. If the process exits immediately, read the validation error; it is usually a
project/key mismatch or a missing payment key.
Check it:
curl -s http://localhost:3001/health | jq
# {"status":"ok","appEnv":"dev","production":false, ...}
3. Start the admin API
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-admin-api
npm ci
APP_ENV=dev npm run dev # port 3003
The admin API also starts:
- the RTDB claim consumers (
resolutionEvents,transcodeFailed, legacyimageEvents), unlessRESOLUTION_CONSUMER_DISABLED=true; - the notification hub (SSE), always;
- the VM lifecycle timer.
The claim consumers are child_added queues with no lease. Running a second admin API against
the same database double-processes events. If you only need to read admin routes, set
RESOLUTION_CONSUMER_DISABLED=true in your local env and leave the VM lifecycle alone.
Never disable the consumer in production.
4. Point the SPA at the local API
For the website, set the API base used by src/lib/apiClient.ts to
http://localhost:3001. The variable is VITE_API_BASE_URL in the repository's env files. Make
sure the API's CORS origin list includes your dev origin; otherwise the browser request fails
before it reaches a controller.
For the webview, the API base is constructed in src/config/env.ts from the mode. The default in
development is the test origin, which is usually what you want.
Level 3 — media and uploads
Only do this if your task touches the upload, transcode or HLS path.
Multimedia
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-multimedia
npm install
npm start
The container needs:
- an output bucket mounted with gcsfuse;
- a GCS service-account key mounted at runtime;
/dev/fuseandSYS_ADMIN(orprivileged: true) in Docker;HTTP_PORT=8080,EXPRESS_PORT=8000,RTMP_PORT=1935.
Running npm start outside the container only works if the bucket is already mounted at the
path the cache expects. The Compose definition in
vps-reverse-proxy/stacks/greyscreentv-test/docker-compose.yml is the reference.
Worker
The worker is designed to be created by the admin API, not run by hand. If you must run it:
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-vps
npm ci
npm run build
npm start
It needs RTDB_URL, the input and output buckets mounted with gcsfuse, DEFAULT_SEG_DURATION,
and a Firebase key. ENABLE_VM_SELF_DELETE should be false outside GCE, otherwise its
shutdown path tries to delete an instance that does not exist.
There is one RTDB queue per database, not per laptop. A local worker that points at the test stack's database competes with the test worker. Point it at an isolated RTDB or do not start it.
Native apps
The phone shell and TV app are Expo projects:
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/greyscreen-expo-webview
npm install
npm start # Expo dev server, then press a / i
cd /shared/from_machine_c/PROJECTS/GREYSCREEN/DEV_PROCESS/gstv-tv
npm install
npm start
Use the test variant (APP_VARIANT=test or the preview EAS profile). Production builds pick
the production Firebase key from firebase_keys/, which you should not need locally.
Smoke test
After the stack is up, verify in this order:
GET /healthon both APIs returns{"status":"ok","appEnv":...}.GET /healthzon each static frontend returns 200.GET /healthon multimedia returns 200 (if running).- The website loads the home rails;
/moviesshows the catalogue. - A trailer page plays a trailer without signing in.
- Sign-in works for the website; the admin login works for the back office.
- Only then test the flow you changed.
If a catalogue call returns an empty list, check the Firebase project first, then the
isReady flag on the title, then the API logs — not the React code.
Common local commands
| Repository | Command | Notes |
|---|---|---|
gstv-website | npm run dev, npm run build:test, npm run typecheck | Vite mode picks the environment. |
greyscreen-webview | npm run dev, npm run build, npm run lint | No typecheck script. |
gstv-admin-web | npm run dev, npm run build:test | JavaScript project; no tsconfig. |
gstv-customer-api | APP_ENV=dev npm run dev, npm run build | Needs .env layers. |
gstv-admin-api | APP_ENV=dev npm run dev, npm run build | Single replica only. |
gstv-vps | npm run build, npm start | Container needs gcsfuse. |
gstv-multimedia | npm start | Container needs gcsfuse. |
greyscreen-expo-webview | npm start, npm run android, npm run ios | EAS profiles in eas.json. |
gstv-tv | npm start, npm run android, npm run build:dev | EAS/Play distribution. |
Next: Per-repository guide → Troubleshooting.