Prod hardening: one-game-per-player, selectable music, bargevasat.ir config
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 7m47s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m9s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 1s

- One running game per player: server rejects a 2nd matchmake while in a live
  room (re-syncs the existing game); client guards Home vs-computer + Lobby
  random/create — resumes the running match + notifies instead of starting another
  (game-store hasActiveMatch()).
- Background music is now selectable: santoor (سنتی, calm Persian loop) and
  playful (bouncy UNO-like) — sound.ts TRACKS + setMusicTrack (persisted),
  sound-store musicTrack, picker in Profile → Audio. i18n added.
- Production config for bargevasat.ir (prepare-only; no live deploy):
  appsettings.Production.example (CORS + ZarinPal + IAB to the domain),
  docker-compose.caddy.yml + Caddyfile (auto-HTTPS reverse proxy
  bargevasat.ir→web, api.bargevasat.ir→server), ENV_FILE PRODUCTION block,
  PRODUCTION.md go-live + Cafe Bazaar publish/IAB checklist. Fixed IAB package
  name to match Capacitor appId (com.bargevasat.app).

Verified: tsc + next build + dotnet build all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-06 23:05:52 +03:30
parent 265d878f22
commit e49df07c0f
13 changed files with 268 additions and 17 deletions
+57
View File
@@ -0,0 +1,57 @@
# Production go-live — bargevasat.ir + Cafe Bazaar
Companion to `HANDOFF.md` / `DEPLOY.md`. Domain: **bargevasat.ir** (web) +
**api.bargevasat.ir** (.NET SignalR API). Android via **Cafe Bazaar**.
## 1. DNS + firewall (you do this)
- A-records → your server IP: `bargevasat.ir`, `www.bargevasat.ir`, `api.bargevasat.ir`.
- Open ports **80** + **443** (`ufw allow 80 && ufw allow 443`).
## 2. Production env (Gitea `ENV_FILE` secret)
Use the **PRODUCTION block** in `deploy/ENV_FILE.example`:
- `NEXT_PUBLIC_SERVER_URL=https://api.bargevasat.ir` (baked at web build → needs a CI rebuild to change)
- `CORS_ORIGINS=https://bargevasat.ir,https://www.bargevasat.ir`
- `JWT_KEY` = `openssl rand -hex 32`, strong `POSTGRES_PASSWORD`
- ZarinPal **live**: `ZARINPAL_SANDBOX=false`, live merchant id, callback `https://api.bargevasat.ir/api/coins/pay/callback`, return `https://bargevasat.ir`
- ZarinPal panel: register the callback domain.
## 3. Deploy with HTTPS (Caddy)
The deploy job (or you, on the server) runs the stack **with the Caddy overlay**:
```bash
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
```
Caddy auto-provisions Let's Encrypt certs and proxies `bargevasat.ir → web`,
`api.bargevasat.ir → server`. SignalR WebSockets pass through transparently.
(To wire this into CI, add `-f docker-compose.caddy.yml` to the deploy job's
compose commands once DNS resolves.)
## 4. Database (Supabase or the bundled Postgres)
- Bundled `db` service works for launch. For Supabase: set `Database__Provider=postgres`
+ the Supabase `ConnectionStrings__Default`, and **generate EF migrations** first
(`HANDOFF.md` §5.1) so the server runs `Migrate()` instead of `EnsureCreated()`.
- **Back up before every deploy** (the deploy job already `pg_dump`s).
## 5. Cafe Bazaar (Android) publish
1. **Build a signed release APK/AAB**`NEXT_PUBLIC_STORE=bazaar`,
`NEXT_PUBLIC_APP_PACKAGE=com.bargevasat.app`, `NEXT_PUBLIC_USE_SERVER=1`,
`NEXT_PUBLIC_SERVER_URL=https://api.bargevasat.ir`, then `npm run cap:sync` +
build in Android Studio / gradle (see `ANDROID.md`). App id **com.bargevasat.app**.
2. Upload to **pardakht.cafebazaar.ir**, fill the listing (icon, screenshots, fa
description), submit for review.
3. **In-app billing (after approval):** in the Bazaar dev panel create the coin
SKUs (`p1``p4`, matching `ProfileService.Packs`), create the **Pardakht API**
OAuth client, do the one-time consent to get a **refresh token**, and put
`IAB_BAZAAR_CLIENT_ID/SECRET/REFRESH_TOKEN` (+ `IAB_PACKAGE_NAME=com.bargevasat.app`)
into `ENV_FILE`; set `IAB_ALLOW_UNVERIFIED=false`. The web client deep-links
`bazaar://in_app?...` and the server verifies the returned `purchaseToken`
before crediting (see `HANDOFF.md` §5.3 / `src/lib/storeBilling.ts`).
4. Set `Zarinpal__Sandbox=false` only for the **web/PWA** payment path; the
**store build uses IAB, not ZarinPal** (store policy).
## 6. Pre-launch hardening checklist
- [ ] `JWT_KEY` is a real 32+ char secret (compose `${JWT_KEY:?}` fails if unset).
- [ ] `IAB_ALLOW_UNVERIFIED=false`, `ZARINPAL_SANDBOX=false`.
- [ ] CORS = the real domains only (no localhost).
- [ ] DB backups confirmed (`/opt/hokm-backups`), volumes named (no orphan data — see DEPLOY.md incident rules).
- [ ] CI green: tsc + next build + dotnet build + Hokm.Sim.
- [ ] Smoke test on https://bargevasat.ir: OTP login, vs-AI game, ranked match, buy-coins redirect, friends/chat.