Files
flatrender/deploy/README.md
T
soroush.asadi 62ea110605
CI/CD / CI · Web (tsc) (push) Successful in 1m33s
CI/CD / Deploy · full stack (push) Failing after 20s
feat(payment): admin-editable ZarinPal settings + in-panel test payment
Lets the broker's ZarinPal merchant / sandbox / amount-unit be set from
Admin → درگاه پرداخت (persisted in payment.settings) instead of env +
redeploy, and adds a per-app "test payment" button that mints a real
ZarinPal StartPay link straight from the panel — no site wiring needed.

- migration 33_payment_settings.sql: singleton payment.settings + a
  transactions.is_test column. (33, not 32 — 32 is content_render_engine.)
- broker read-path precedence: per-client override > DB settings > env.
- POST /v1/admin/clients/:id/test-payment + GET/PUT /v1/admin/settings.
- admin UI: «تنظیمات زرین‌پال» tab + «پرداخت آزمایشی» button.

Adversarial-review fixes (2 confirmed HIGH):
- do NOT pre-seed the settings row — a seeded sandbox=TRUE default would
  override a production ZARINPAL_SANDBOX=false env and silently route real
  payments to sandbox.zarinpal.com until an admin untouched the toggle.
  No row → env governs until an admin saves.
- test transactions are tagged is_test and the webhook dispatcher skips
  them, so an admin smoke-test can never notify (or credit) a real client,
  regardless of metadata. Broker-authoritative, not consumer-dependent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:47:10 +03:30

108 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deploying FlatRender (Gitea CI/CD → 171.22.25.73, behind mirror-nginx)
Push to **Gitea** triggers `.gitea/workflows/ci-cd.yml`: a frontend `tsc` check, then a
self-hosted `deploy` job that builds the whole compose stack and brings it up. The
existing central **mirror-nginx** (owns 80/443, manual TLS certs) reverse-proxies the
three public domains to FlatRender's host ports — FlatRender does **not** run Caddy here.
GitHub (`origin`) stays a backup and never deploys.
Stack: gateway · identity · content · studio (.NET/Go) · file · render · notification
(Go) · Next.js frontend · Postgres · MinIO. All package installs route through
`mirror.soroushasadi.com` (Nexus).
```
mirror-nginx (:443, /etc/ssl/flatrender)
flatrender.ir → 171.22.25.73:1600 (fr2-frontend)
api.flatrender.ir → 171.22.25.73:1605 (fr2-gateway)
pay.flatrender.ir → 171.22.25.73:1607 (fr2-payment, ZarinPal broker)
storage.flatrender.ir → 171.22.25.73:1610 (fr2-minio)
```
The **payment broker** (`fr2-payment`, `pay.flatrender.ir`) is a standalone generic
ZarinPal gateway shared by FlatRender + meezi.ir + bargevasat.ir — ZarinPal only
accepts callbacks on that one verified domain. It does NOT sit behind the API
gateway (clients authenticate with an API key + HMAC). See
[`PAYMENTS.md`](./PAYMENTS.md) for the integration contract. The `payment` schema
is migrations `31_payment_broker.sql` (tables) + `33_payment_settings.sql`
(admin-editable ZarinPal config + `transactions.is_test`) — apply BOTH, in order,
on an existing DB volume (migrations only auto-run on first volume creation):
```
docker exec -i fr2-postgres psql -U flatrender -d flatrender < backend/db/migrations/31_payment_broker.sql
docker exec -i fr2-postgres psql -U flatrender -d flatrender < backend/db/migrations/33_payment_settings.sql
```
The broker image expects `is_test` (migration 33) — deploy it together with both migrations.
## One-time setup (do these BEFORE the first `git push gitea master`)
1. **DNS** — this box sits BEHIND NAT: its interface IP is `171.22.25.73` (private),
public NAT IPs are `31.171.101.127/.211`, and inbound 443 normally arrives via the
edge/CDN `185.239.1.100` (same entry your other sites use, e.g. `meezi.ir`). So a new
domain must enter the SAME way the others do — either:
- register `flatrender.ir` + `api` + `pay` + `storage` + `www` in that edge/CDN (origin =
this server) and point DNS there, **or**
- bypass the CDN and point DNS straight at the server's public IP (like the hokm `api`
subdomain does — "must bypass").
Pointing DNS at a random/registrar IP shows that host's default page (e.g. a "not
licensed" page), NOT FlatRender.
2. **TLS cert** — ⚠️ mirror-nginx mounts cert dirs INDIVIDUALLY, so a fresh
`/etc/ssl/flatrender/` on the host is invisible inside the container. **Nest the cert
under an already-mounted dir** (the conf references this path):
```bash
mkdir -p /etc/ssl/soroushasadi/flatrender
cp <yourcert>/fullchain.pem /etc/ssl/soroushasadi/flatrender/
cp <yourcert>/privateKey.pem /etc/ssl/soroushasadi/flatrender/
```
Cert must cover `flatrender.ir` + `api.` + `pay.` + `storage.` (wildcard `*.flatrender.ir` + apex, or SAN).
3. **mirror-nginx** — add the server blocks from [`mirror-nginx-flatrender.conf`](./mirror-nginx-flatrender.conf)
to the proxy's `http{}` (the host file is `/root/mirror-server/nginx/nginx.conf`), then:
`docker exec mirror-nginx nginx -t && docker exec mirror-nginx nginx -s reload`.
⚠️ If you edited the conf with `sed -i` (which swaps the file inode), the running
container keeps the old inode → `docker restart mirror-nginx` instead (~3s blip).
Verify locally (bypasses DNS): `curl -sk --resolve flatrender.ir:443:127.0.0.1 https://flatrender.ir/ | head -c 60`
must show `<html lang="fa" dir="rtl">`. (Do this after the first deploy is up, or it 502s.)
4. **ENV_FILE secret** — at `…/soroushdes/flatrender/settings/secrets`, create `ENV_FILE`
from [`ENV_FILE.production.example`](./ENV_FILE.production.example) (already filled for
flatrender.ir; generate each secret with `openssl rand -hex 32`).
5. **Gitea Actions** enabled for this repo; act_runner has the `self-hosted:host` label
(the standard box already has this). daemon.json already mirrors Docker Hub via Nexus.
## Go live
```bash
git push gitea master # triggers CI + deploy
```
Watch `https://git.soroushasadi.com/soroushdes/flatrender/actions`. First run ~1525 min
(cold Nexus cache + all images build). When the deploy is green, add/reload the nginx
blocks (step 3) and visit `https://flatrender.ir`.
## Host ports (must be free on 171.22.25.73)
`1600` frontend · `1605` gateway · `1607` payment broker · `1610` MinIO · `1611` MinIO
console. Postgres (5432) and render (5010) bind to `127.0.0.1` only. Avoid `:3000` (Gitea),
`:8081-8083` (Nexus), `:1500/1505/1520` (bargevasat), `:3010/3101-3103/5080/5081` (meezi),
`:3020`, `:2569`. Change them via `FRONTEND_PORT`/`GATEWAY_PORT`/`PAY_PORT`/`MINIO_PORT` in
the secret if any collide.
## First-run notes
- **Migrations** auto-run once via `deploy/postgres-initdb/00-init.sh` (mounted as the
whole `/docker-entrypoint-initdb.d` directory — a single-file bind mount left a stale
empty dir → "Is a directory") when the Postgres volume is first created. Later schema
changes are applied manually with `psql` (the volume persists). ⚠️ If a deploy ever
ran with a wrong/empty secret, the volume bakes the wrong password + may skip init →
`docker rm -f fr2-postgres && docker volume rm flatrender_pgdata` (only while there's
no real data) then re-run, so it re-inits with the current password + migrations.
- **Rendering** — no After Effects node on the server, so `RENDER_DEV_WORKER=false`.
Disable rendering in **Admin → فارم رندر → موتور رندر** so users see an "unavailable"
notice instead of jobs that never finish. Point real render nodes at the server later.
- **MinIO public URLs** — verify an uploaded image + a render download resolve over
`https://storage.flatrender.ir`. If not, recheck `MINIO_HOST_ENDPOINT` /
`MINIO_HOST_USE_SSL` / `NEXT_PUBLIC_MINIO_URL` in the secret and redeploy.
## Redeploy / rotate secrets
Edit `ENV_FILE` in Gitea (or push any commit) → the deploy re-runs. It backs up the DB to
`/opt/flatrender-backups/` before each deploy and never runs `docker compose down -v`.
Changing a `NEXT_PUBLIC_*` value only takes effect after the redeploy (baked at build).