Environments
GreyScreen has three logical environments (dev, test, prod) and two isolated
Firebase identities in practice. The gap between "test" and "production" is narrower than the
names suggest; read the caveats before pointing anything at a live service.
Identity table
| dev | test | prod | |
|---|---|---|---|
| Public origin | http://localhost:* | https://test.greyscreentv.com | https://greyscreentv.com, https://www.greyscreentv.com |
| Firebase project (per env files) | greyscreen-test-5fb86 | greyscreen-test-5fb86 | grey-screen-tv |
| Deployed test stack actually points at | — | grey-screen-tv (production) | grey-screen-tv |
| GCP project | grey-screen-mobile-and-web-app | grey-screen-mobile-and-web-app | grey-screen-mobile-and-web-app |
| GCS bucket suffix | -test | -test | -prod |
| Test VPS | — | 82.38.44.60 | production hosts |
The dev and test layers share a trust domain: .env.nonprod.local is common to both,
with .env.development.local or .env.test.local overlaid for endpoint differences. Production
loads .env.production.local only. Only .env.template is tracked; the .local files are
gitignored and excluded from Docker builds.
APP_ENV is not NODE_ENV. APP_ENV selects the environment contract; NODE_ENV selects
build/runtime behaviour. The APIs validate both at boot.
The two traps
vps-reverse-proxy/stacks/greyscreentv-test/docker-compose.yml currently points the test APIs'
.env.test.local at grey-screen-tv. Only the GCS buckets are separate. Isolation comes
from RESOLUTION_CONSUMER_DISABLED=true (so the test API does not become a second claim
consumer) and from the Compose vps profile (which keeps the test worker from racing the
production worker). It is not a separate database.
Consequences:
- A write made through the test stack is a production write.
- Do not run destructive tests against the test stack.
- The test API's
APP_ENVmay betestwhile its Firebase key validates againstprod; the validation rules follow the project the key names.
gstv-website/src/Firebase/FirebaseConfig.js is not env-driven. On main it points at
grey-screen-tv; on test it points at greyscreen-test-5fb86. public/firebase-messaging-sw.js
carries the same project. The native apps follow a build-variant equivalent in app.config.ts.
A wholesale merge of test into main would silently regress production identity. Releases
are cherry-picks. See Branch strategy.
Payments by environment
| Provider | dev/test | prod | Notes |
|---|---|---|---|
| Stripe | sk_test_… required | sk_live_… required | Boot fails on a prefix mismatch. Price IDs do not encode mode; confirm in the Stripe dashboard. |
| Flutterwave | production merchant | production merchant | There is no sandbox merchant. Treat every mobile-money charge as real. |
gstv-website/.env.production carries a live publishable key while its comments still mention a
sandbox. The comments are stale; do not "restore sandbox keys" based on them.
Ports
| Service | dev | test / prod |
|---|---|---|
gstv-website | 5173 | 80 (container) |
greyscreen-webview | 5174 | 80 (container) |
gstv-admin-web | 5173 (does not run at the same time as the website) | 80 (container) |
gstv-customer-api | 3001 | 3000 |
gstv-admin-api | 3003 | 3003 |
gstv-multimedia | 8000 + 8080 + 1935 | same |
| Gateway | — | 80 / 443 |
The webview's dev port is deliberately different from the website's because Stripe return URLs must be unambiguous.
What runs where
| Component | dev | test | prod |
|---|---|---|---|
| SPAs | Vite dev server | Static nginx container | Static nginx container |
| APIs | npm run dev | Docker image, :test | Docker image, :prod |
| Multimedia | Usually not run locally | Container with gcsfuse | Container with gcsfuse |
| Worker | Only with explicit intent | Compose vps profile, usually off | GCE VM, created on demand |
| Native apps | Expo dev client | EAS test/preview variant | EAS production build |
Validation at boot
Both APIs validate their environment and refuse to start on mismatch. The list is not negotiable; expect the process to exit rather than serve with a half-configured identity.
- A readable service-account JSON.
- Firebase project matching the environment (
greyscreen-test-5fb86for dev/test,grey-screen-tvfor prod). - Stripe secret key prefix matching the environment.
- Stripe webhook secret and price IDs; Flutterwave keys.
ORIGIN/CORS_ORIGINnot*in production.- A valid
APP_ENVandNODE_ENV. - Admin API only: bucket suffixes, RTDB host/project match, and
RESOLUTION_CONSUMER_DISABLEDnot set totruein production.
The customer API warns about a weak SECRET_KEY; the admin API does not block
ADMIN_AUTH_DISABLED=true in production. Treat both as open items, not as safe defaults.
Public path layout
Both public origins expose the same paths:
| Path | Upstream | Prefix stripped |
|---|---|---|
/ | website | no |
/webview/ | webview SPA | yes |
/admin/ | admin SPA | yes |
/api/customer/ | customer API | yes |
/api/admin/ | admin API | yes |
/stream/ | multimedia | yes |
Everything else on the host is routed by domains.json. Do not assume a new service is public
until a route exists in that file and generate.sh has run.
Next: Running locally.