Files
api/deploy/DEPLOY.md
T
mohmdsdgandCursor 16d562caa5 feat(replicas): auto-heal slave data drift and no-store public settings
Add sync-replica-data repair on deploy, a publisher-side replica watch
(60s + after tenant/settings writes), REFRESH PUBLICATION after migrate,
and Cache-Control no-store on program-settings and get_version.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 12:32:30 +03:30

197 lines
6.4 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.
# Deploy — SuperAdmin on shared montime host
Host: `185.50.38.53` (SSH port `3031`)
Domain: `https://adm.montzar.ir`
DB: Postgres `superadmin` (owner `montime`) — already created
API port: `3010` (3000=montime, 3001=forisanad)
## Layout on server
```text
/var/www/superadmin/
api/ # clone of controlPanel/api.git
deploy/ # nginx + pm2 + this guide (in api repo)
web/ # clone of controlPanel/web.git (Vue dist)
```
## 1) Clone / pull
```bash
sudo mkdir -p /var/www/superadmin
sudo chown montime:montime /var/www/superadmin
cd /var/www/superadmin
git clone https://get.feep.ir/controlPanel/api.git api
git clone https://get.feep.ir/controlPanel/web.git web
```
Later updates: `git pull` in each folder.
## 2) API `.env`
```bash
cd /var/www/superadmin/api
cp .env.example .env
nano .env
```
Required:
```env
NODE_ENV=production
PORT=3010
DATABASE_URL="postgresql://montime:YOUR_PASSWORD@127.0.0.1:5432/superadmin?schema=public"
JWT_SECRET="long-random-production-secret"
JWT_EXPIRES_IN="7d"
CORS_ORIGIN="https://adm.montzar.ir"
PUBLIC_API_URL="https://adm.montzar.ir"
SEED_ADMIN_MOBILE="09128081392"
SEED_ADMIN_PASSWORD="change-me"
# CENTRAL_SMS_* as needed
```
Password = same Postgres password that works for `psql -h 127.0.0.1 -U montime -d superadmin`.
## 3) Install, migrate, build, seed
```bash
cd /var/www/superadmin/api
npm ci
npx prisma generate
npx prisma migrate deploy
npm run build
npm run prisma:seed
# optional first import:
# npm run prisma:import-users
```
## 4) PM2
```bash
pm2 start /var/www/superadmin/api/deploy/ecosystem.config.cjs
pm2 save
pm2 status
curl -s http://127.0.0.1:3010/api/health || true
```
## 5) Web build
Same-origin: leave `VITE_API_BASE` unset (defaults to `/api`).
```bash
cd /var/www/superadmin/web
npm ci
npm run build
# dist → /var/www/superadmin/web/dist
```
## 6) Nginx
```bash
sudo cp /var/www/superadmin/api/deploy/nginx-adm.montzar.ir.conf /etc/nginx/sites-available/adm.montzar.ir
sudo ln -sf /etc/nginx/sites-available/adm.montzar.ir /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
SSL: if Certbot already issued for `adm.montzar.ir`, paths in the conf should match. Otherwise:
```bash
sudo certbot --nginx -d adm.montzar.ir
```
## 7) Smoke
1. Open `https://adm.montzar.ir` → login
2. `https://adm.montzar.ir/get_user?domain=montpar.ir` (or any known domain)
3. `https://adm.montzar.ir/get_settings?domain=k.montzar.ir` (nginx must proxy `/get_settings` like `/get_user`)
4. Confirm uploads/logo URLs use `PUBLIC_API_URL`
## 8) Migrations with logical replication (failover replicas)
`superadmin_pub` streams rows to the failover hosts (`adm.montech.ir`, `adm.montest.ir`).
Logical replication does **not** carry DDL: a migration that adds a column on the
publisher makes every subscriber fail with `missing replicated columns` and the
subscription stalls until that column exists there too. Because `_prisma_migrations`
is replicated, `prisma migrate deploy` on a subscriber wrongly reports
"No pending migrations".
So never run `migrate deploy` alone. On the publisher host:
```bash
cp deploy/replicas.env.example deploy/replicas.env # once; add real DSNs
bash deploy/sync-replica-schema.sh --init # once, when schemas already match
```
For every release on **publisher** (`/var/www/superadmin/api`):
```bash
git pull
npm run replicas:sync # 1) DDL on replicas 2) migrate deploy 3) repair data drift if any
npx prisma generate
npm run build
pm2 restart superadmin-api
```
Check only:
```bash
npm run replicas:check # schema + subscription + row counts (settings, tenants, …)
```
Force data realign from publisher (rare — e.g. after fixing subscriber permissions):
```bash
npm run replicas:repair-data
```
`replicas:sync` runs `sync-replica-data.sh --repair-if-needed` automatically when
`settings` / `setting_templates` / `tenants` counts differ from publisher.
On the **publisher API** (`superadmin-api`), a replica watch also runs: ~8s after
boot, then every 60s, and immediately after tenant/settings writes. Drift is
healed without an operator command. Logical replication still streams live
INSERT/UPDATE/DELETE; the watch only fills **missed history** (WAL is not
rewound).
Each replica API user (`montime` by default) needs `SELECT` on all tables — the
repair script re-applies grants. `REPLICA_DSNS` must connect as a role that can
`TRUNCATE` + `ALTER SUBSCRIPTION` on the subscriber. The watch is idle unless
`deploy/replicas.env` exists on that host.
Keep migrations schema-only. After each migrate, subscribers get
`ALTER SUBSCRIPTION … REFRESH PUBLICATION WITH (copy_data = true)` so **new
tables** join replication (Postgres does not add them by itself).
## 9) Data changes **without** deploy (import settings, edit tenant, …)
**Normal path:** publisher writes → `superadmin_pub` WAL → slaves apply in
**seconds**. No `replicas:sync` for that.
**Why LSN-healthy slaves can still have empty `settings`:** logical replication
is a **stream**, not a mirror. `received_lsn = latest_end_lsn` means “caught up
with current WAL”, not “every historical row exists”. Rows inserted while apply
was broken, or while the table was **not yet in the subscription**, are never
replayed. Postgres will not go back.
That gap is healed **automatically on the publisher API** (watch every 60s +
right after import/edit). You do **not** run `replicas:repair-data` by hand
once this build is running on `/var/www/superadmin/api`.
| Action | What actually happens |
|--------|------------------------|
| INSERT/UPDATE/DELETE while subscription is ready | Live WAL — slaves update immediately |
| New table after `FOR ALL TABLES` publication | Not replicated until `REFRESH PUBLICATION` (deploy `replicas:sync` does this) |
| Missed history (this tenant’s empty settings) | Watch compares counts and recopies from publisher, then live WAL resumes |
`npm run replicas:check` / `replicas:repair-data` remain for operators; they are
not required for day-to-day imports after the watch is up.
## Local development note
Prisma provider is **PostgreSQL** only. Local options:
- Docker: `docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=superadmin postgres:16`
- Or point `DATABASE_URL` at the server DB only for short tests (prefer local DB)
SQLite history is kept under `prisma/migrations_sqlite_archive/` (reference only; not applied by `migrate deploy`).