Files
api/deploy/DEPLOY.md
T
mohmdsdgandCursor 515e39d420 feat(deploy): sync schema to replication subscribers before migrating
Logical replication carries rows but not DDL, so a migration that adds a
column stalls every subscriber with "missing replicated columns" until the
column exists there too. _prisma_migrations is replicated, so migrate deploy
on a subscriber reports nothing pending while its tables lag behind.

sync-replica-schema.sh applies pending migrations to each subscriber first,
tracking them locally in _replica_ddl_applied, then migrates the publisher.
--check reports subscription state and columns missing on any replica.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 21:34:02 +03:30

3.8 KiB

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

/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

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

cd /var/www/superadmin/api
cp .env.example .env
nano .env

Required:

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

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

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).

cd /var/www/superadmin/web
npm ci
npm run build
# dist → /var/www/superadmin/web/dist

6) Nginx

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:

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. 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:

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:

npm run replicas:sync    # DDL to each replica first, then migrate deploy here
npm run replicas:check   # subscription state + columns missing on any replica

Keep migrations schema-only. Data backfills would run once per replica and then again through replication, so apply those on the publisher by hand.

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).