# 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 the publisher (`/var/www/superadmin/api`) and on each replica API directory: ```bash npm run update ``` On the publisher this pulls, installs, applies DDL on the replicas before `prisma migrate deploy`, refreshes subscriptions with `copy_data=false`, builds, and restarts `superadmin-api`. On a replica (no `deploy/replicas.env`) the same command only pulls, installs, generates, builds, and restarts. It does not run `sync-replica-data.sh`. Check only: ```bash npm run replicas:check # schema + subscription + row counts (settings, tenants, …) ``` Do not use `npm run replicas:sync` or `npm run replicas:repair-data` for a release. Both run the data repair, which disables a subscription and recopies rows without advancing the replication origin. The apply worker then dies replaying old WAL. `npm run update` is the release command. `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 = false)` so **new tables** join the subscription without a second full copy (repair already copied rows; a second copy unique-conflicts and stalls live UPDATEs). ## 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`).