From a301034b33f2d098a4f72ddc3fc924824ea14d7d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Jun 2026 19:03:58 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20add=20SETUP=5FKEYS.md=20=E2=80=94=20ste?= =?UTF-8?q?p-by-step=20keys/accounts=20checklist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://claude.ai/code/session_01AMiTSxG8rB8FU8QgJ3Fnhs --- SETUP_KEYS.md | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 SETUP_KEYS.md diff --git a/SETUP_KEYS.md b/SETUP_KEYS.md new file mode 100644 index 0000000..8d3c116 --- /dev/null +++ b/SETUP_KEYS.md @@ -0,0 +1,165 @@ +# Setup Keys & Accounts β€” Step-by-Step Checklist + +A from-zero walkthrough to obtain every secret and account this app needs, with the +exact value β†’ environment-variable mapping. Tick each box as you go. + +There are **three kinds** of values: + +- πŸ” **Self-generated** β€” random strings you create locally. No signup. +- 🧩 **Account-supplied** β€” copied from a provider dashboard. +- πŸ–₯️ **Infra-supplied** β€” produced when you provision a server (e.g. its public IP). + +> **Fastest path:** you can run the *entire* stack locally with only the +> πŸ” self-generated values (see Β§0 and the "Run locally first" box at the end). +> Sign up for the paid providers only when you're ready for production. + +--- + +## 0. Self-generated secrets (do this first β€” no accounts) + +Generate each value and paste it into `.env`. `JWT_SECRET` and `SIGNALING_SECRET` +**must be identical** everywhere they appear (API ⇄ signaling). + +```bash +openssl rand -hex 32 # β†’ JWT_SECRET +openssl rand -hex 32 # β†’ SIGNALING_SECRET (share with signaling service) +openssl rand -hex 32 # β†’ TURN_AUTH_SECRET (coturn static-auth-secret) +openssl rand -hex 16 # β†’ REDIS_PASSWORD +``` + +- [ ] `JWT_SECRET` set (same value in API and signaling) +- [ ] `SIGNALING_SECRET` set (same value in API and signaling) +- [ ] `TURN_AUTH_SECRET` set (β†’ coturn `static-auth-secret`) +- [ ] `REDIS_PASSWORD` set + +--- + +## 1. Domain 🧩 (~$10–15/yr) + +Buy a domain at any registrar (Cloudflare Registrar, Namecheap, Porkbun). + +- [ ] Domain purchased β†’ gives you `app.`, `api.`, `signaling.`, `turn.`, `sfu1.` subdomains +- [ ] Set `APP_URL=https://app.` +- [ ] Set `CORS_ORIGINS=https://app.` +- [ ] Set `API_URL=https://api.` (frontend BFF β†’ API) +- [ ] Set `PUBLIC_SIGNALING_URL=wss://signaling./ws` + +--- + +## 2. Cloudflare (DNS + TLS + WAF) 🧩 (free) + +Sign up at cloudflare.com β†’ **Add a site** β†’ point your registrar's nameservers at +Cloudflare. No paid key required. + +- [ ] Domain added to Cloudflare; nameservers updated +- [ ] DNS records created (see `docs/MEDIA_INFRASTRUCTURE.md` Β§8): + - [ ] `app` / `api` / `signaling` β†’ **proxied** (orange cloud) OK + - [ ] `turn` and `sfu1` β†’ **DNS-only** (grey cloud) β€” WebRTC UDP can't be proxied +- [ ] TLS mode set to **Full (strict)** + +--- + +## 3. Hetzner Cloud (servers β€” Path A self-host) πŸ–₯️ (~$30–60/mo per box) + +Sign up at console.hetzner.cloud β†’ create a project β†’ create servers. + +- [ ] **SFU box**: CCX23 or CCX33 (**dedicated** vCPU) β€” note its public IPv4 +- [ ] **App box**: CX32 / CPX31 (API + frontend + signaling + Redis) +- [ ] **TURN**: co-locate on SFU box, or a small CX22 β€” note its public IPv4 +- [ ] Set `SFU_ANNOUNCED_IP=` +- [ ] Set `SFU_NODES=sfu1.:4000` (how signaling reaches the SFU) +- [ ] Set `TURN_EXTERNAL_IP=` (coturn `external-ip`) +- [ ] Firewall: open `40000–49999` UDP+TCP (SFU), `3478`/`5349` + relay range (TURN), + `80`/`443` (app). See `docs/MEDIA_INFRASTRUCTURE.md` Β§4. + +> Redis and coturn are **self-hosted** from `infrastructure/docker/docker-compose.yml` +> β€” no Redis/TURN SaaS keys needed unless you choose to outsource them. + +--- + +## 4. Neon (Postgres) 🧩 (free to start β†’ ~$19/mo) + +neon.tech β†’ sign up β†’ **New Project**. + +- [ ] Project created +- [ ] Console β†’ **Connection Details** β†’ copy the **pooled** connection string +- [ ] Set `DATABASE_URL=postgresql://…@…neon.tech/neondb?sslmode=require` +- [ ] Run migrations: `sqlx migrate run --source backend-rs/crates/api/migrations` (or `make migrate`) +- [ ] Seed the `plans` table (SQL in `SUBSCRIPTIONS_AND_TIERS.md` Β§2.3) + +--- + +## 5. Cloudflare R2 (file storage) 🧩 (free 10 GB β†’ usage) + +In the Cloudflare dashboard β†’ **R2** β†’ **Create bucket**, then **Manage R2 API Tokens**. + +- [ ] Bucket created β†’ set `R2_BUCKET=` +- [ ] API token created β†’ set `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY` +- [ ] Set `R2_ENDPOINT=https://.r2.cloudflarestorage.com` +- [ ] Set `R2_REGION=auto` +- [ ] (Optional public bucket) set `R2_PUBLIC_URL=https://files.` + +--- + +## 6. Stripe (billing) 🧩 (no monthly fee; 2.9% + $0.30/txn) + +stripe.com β†’ sign up β†’ **stay in Test mode** until you've verified the flow. + +- [ ] Developers β†’ **API keys**: + - [ ] Secret key (`sk_test_…`) β†’ `STRIPE_SECRET` + - [ ] Publishable key (`pk_test_…`) β†’ `STRIPE_KEY` (frontend) +- [ ] Create **Products + monthly/yearly Prices** for Starter / Professional / Business + (`SUBSCRIPTIONS_AND_TIERS.md` Β§2.2) β†’ put the `price_…` IDs in the `plans` table +- [ ] Developers β†’ **Webhooks** β†’ add endpoint `https://api./v1/webhooks/stripe` + with events `checkout.session.completed`, `customer.subscription.updated`, + `customer.subscription.deleted` +- [ ] Copy the webhook **signing secret** (`whsec_…`) β†’ `STRIPE_WEBHOOK_SECRET` +- [ ] When live: repeat in **Live mode** and swap to `sk_live_…` / `pk_live_…` + +--- + +## 7. Optional (skip until needed) + +- [ ] **Transactional email** (Resend / Postmark / SES) β€” verification & receipts +- [ ] **Observability** β€” `OTEL_EXPORTER_OTLP_ENDPOINT` β†’ self-hosted Jaeger (in compose) or Grafana Cloud +- [ ] **Sentry** β€” `SENTRY_DSN` + +--- + +## Value β†’ environment-variable quick reference + +| Value | Env var(s) | Source | +|-------|-----------|--------| +| JWT signing secret | `JWT_SECRET` | πŸ” `openssl rand -hex 32` | +| Signaling shared secret | `SIGNALING_SECRET` | πŸ” `openssl rand -hex 32` | +| TURN shared secret | `TURN_AUTH_SECRET` | πŸ” `openssl rand -hex 32` | +| Redis password | `REDIS_PASSWORD` | πŸ” `openssl rand -hex 16` | +| Postgres URL | `DATABASE_URL` | 🧩 Neon | +| Stripe secret / publishable / webhook | `STRIPE_SECRET`, `STRIPE_KEY`, `STRIPE_WEBHOOK_SECRET` | 🧩 Stripe | +| R2 credentials | `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_ENDPOINT`, `R2_BUCKET`, `R2_REGION` | 🧩 Cloudflare R2 | +| SFU public IP | `SFU_ANNOUNCED_IP` | πŸ–₯️ Hetzner SFU box | +| SFU address for signaling | `SFU_NODES` | πŸ–₯️ `sfu1.:4000` | +| TURN public IP | `TURN_EXTERNAL_IP` | πŸ–₯️ Hetzner TURN box | +| App / API / signaling URLs | `APP_URL`, `API_URL`, `CORS_ORIGINS`, `PUBLIC_SIGNALING_URL` | 🧩 your domain | + +--- + +## Run locally first (zero paid accounts) + +```bash +cp .env.example .env # fill in only the Β§0 openssl secrets +make docker-up # Postgres + Redis + signaling + SFU + coturn, all local +``` + +- Postgres, Redis, coturn run in Docker β€” no Neon / Upstash / TURN SaaS needed. +- Stripe: use **test** keys + `stripe listen --forward-to localhost:8080/v1/webhooks/stripe`. +- R2: only needed once you exercise file uploads. + +Verify realtime media: open `chrome://webrtc-internals`, join a room, confirm the +`candidate-pair` is `host`/`srflx` (**not** `relay`) and RTT is low. Details in +`docs/MEDIA_INFRASTRUCTURE.md` Β§10. + +--- + +_Related docs: `SUBSCRIPTIONS_AND_TIERS.md` (services + in-app tiers), +`docs/MEDIA_INFRASTRUCTURE.md` (realtime SFU/TURN), `DEPLOYMENT_GUIDE.md` (full deploy)._