2026-05-28 19:06:28 +03:30
|
|
|
|
# Meezi — Deployment Guide
|
|
|
|
|
|
|
|
|
|
|
|
## Architecture overview
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Server: 171.22.25.73
|
|
|
|
|
|
│
|
|
|
|
|
|
├── Gitea :3000 ← source control + CI runner
|
2026-05-31 11:06:24 +03:30
|
|
|
|
├── Nexus mirror.soroushasadi.com ← package mirror (NuGet, npm, Docker, MCR)
|
2026-05-28 19:06:28 +03:30
|
|
|
|
│
|
|
|
|
|
|
├── meezi-api :5080 ← .NET main API
|
|
|
|
|
|
├── meezi-admin-api:5081 ← .NET admin API
|
|
|
|
|
|
├── meezi-web :3101 ← Next.js cafe owner dashboard
|
|
|
|
|
|
├── meezi-admin-web:3102 ← Next.js super-admin panel
|
|
|
|
|
|
├── meezi-website :3010 ← Next.js marketing website
|
2026-05-29 17:02:22 +03:30
|
|
|
|
├── meezi-koja :3103 ← Next.js public discovery (Koja)
|
2026-05-28 19:06:28 +03:30
|
|
|
|
├── meezi-db :5434 ← PostgreSQL (not internet-facing)
|
|
|
|
|
|
└── meezi-redis :6381 ← Redis (not internet-facing)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Docker Compose files:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-05-29 17:02:22 +03:30
|
|
|
|
docker-compose.yml main services (postgres, redis, api, web, website, koja)
|
2026-05-28 19:06:28 +03:30
|
|
|
|
docker-compose.admin.yml admin overlay (+admin-api, +admin-web)
|
|
|
|
|
|
docker-compose.mirror.yml Nexus mirror — run once separately, stays running
|
|
|
|
|
|
docker-compose.caddy.yml Caddy HTTPS proxy — add when domain is ready
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## First-time setup
|
|
|
|
|
|
|
|
|
|
|
|
### Step 1 — Set the `ENV_FILE` secret in Gitea
|
|
|
|
|
|
|
|
|
|
|
|
Open in browser:
|
|
|
|
|
|
```
|
|
|
|
|
|
http://171.22.25.73:3000/soroushdes/meezi/settings/secrets
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Click **Add Secret**, name it exactly **`ENV_FILE`**, paste the block below, click Save.
|
|
|
|
|
|
|
|
|
|
|
|
```env
|
|
|
|
|
|
ASPNETCORE_ENVIRONMENT=Production
|
|
|
|
|
|
|
|
|
|
|
|
# ── Database ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
DB_PASSWORD=YOUR_STRONG_PASSWORD
|
|
|
|
|
|
DB_CONNECTION_STRING=Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=YOUR_STRONG_PASSWORD
|
|
|
|
|
|
|
|
|
|
|
|
# ── JWT ───────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
# Generate: openssl rand -hex 32
|
|
|
|
|
|
JWT_KEY=YOUR_64_CHAR_HEX
|
|
|
|
|
|
|
|
|
|
|
|
# ── Migrations ────────────────────────────────────────────────────────────────
|
2026-05-27 21:33:10 +03:30
|
|
|
|
RUN_MIGRATIONS=true
|
2026-05-28 19:06:28 +03:30
|
|
|
|
|
|
|
|
|
|
# ── Public URLs ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
# ⚠️ NEXT_PUBLIC_* are baked into Next.js images at build time.
|
|
|
|
|
|
# Changing them requires a CI re-run to rebuild images.
|
|
|
|
|
|
NEXT_PUBLIC_API_URL=http://171.22.25.73:5080
|
|
|
|
|
|
NEXT_PUBLIC_ADMIN_API_URL=http://171.22.25.73:5081
|
|
|
|
|
|
NEXT_PUBLIC_SITE_URL=http://171.22.25.73:3010
|
2026-05-29 17:02:22 +03:30
|
|
|
|
NEXT_PUBLIC_KOJA_URL=http://171.22.25.73:3103
|
2026-05-28 19:06:28 +03:30
|
|
|
|
|
|
|
|
|
|
APP_QR_BASE_URL=http://171.22.25.73:3101
|
|
|
|
|
|
BILLING_DASHBOARD_URL=http://171.22.25.73:3101
|
|
|
|
|
|
|
|
|
|
|
|
# ── CORS ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
CORS_ORIGIN_0=http://171.22.25.73:3101
|
|
|
|
|
|
CORS_ORIGIN_1=http://171.22.25.73:3010
|
|
|
|
|
|
CORS_ORIGIN_2=http://171.22.25.73:3103
|
|
|
|
|
|
CORS_ADMIN_ORIGIN_0=http://171.22.25.73:3102
|
|
|
|
|
|
|
|
|
|
|
|
# ── Host ports ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
API_PORT=5080
|
|
|
|
|
|
ADMIN_API_PORT=5081
|
|
|
|
|
|
WEB_PORT=3101
|
|
|
|
|
|
ADMIN_WEB_PORT=3102
|
|
|
|
|
|
WEBSITE_PORT=3010
|
2026-05-29 17:02:22 +03:30
|
|
|
|
KOJA_PORT=3103
|
2026-05-28 19:06:28 +03:30
|
|
|
|
POSTGRES_PORT=5434
|
|
|
|
|
|
REDIS_PORT=6381
|
|
|
|
|
|
|
|
|
|
|
|
# ── Payment: ZarinPal ─────────────────────────────────────────────────────────
|
|
|
|
|
|
ZARINPAL_MERCHANT_ID=
|
|
|
|
|
|
ZARINPAL_SANDBOX=true
|
|
|
|
|
|
|
|
|
|
|
|
# ── SMS: Kavenegar ────────────────────────────────────────────────────────────
|
|
|
|
|
|
# Empty = OTP printed to API container logs (ok for testing)
|
|
|
|
|
|
KAVENEGAR_API_KEY=
|
|
|
|
|
|
|
|
|
|
|
|
# ── Snappfood webhook ─────────────────────────────────────────────────────────
|
|
|
|
|
|
SNAPPFOOD_WEBHOOK_SECRET=YOUR_RANDOM_SECRET
|
2026-05-27 21:33:10 +03:30
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
> The actual generated values were set directly in Gitea during initial setup.
|
|
|
|
|
|
> To view or rotate them: Gitea → Settings → Secrets → ENV_FILE → Edit.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 2 — Trigger the first deployment
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
On the server:
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
```bash
|
|
|
|
|
|
cd ~/meezi
|
|
|
|
|
|
git pull origin main
|
|
|
|
|
|
git push gitea main
|
|
|
|
|
|
```
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
Watch the pipeline:
|
|
|
|
|
|
```
|
|
|
|
|
|
http://171.22.25.73:3000/soroushdes/meezi/actions
|
2026-05-27 21:33:10 +03:30
|
|
|
|
```
|
2026-05-28 19:06:28 +03:30
|
|
|
|
|
|
|
|
|
|
CI takes ~5–10 minutes: builds 6 Docker images, runs all checks, then deploys.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Service URLs (no domain, IP-based)
|
|
|
|
|
|
|
|
|
|
|
|
| Service | URL |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Marketing website | http://171.22.25.73:3010/fa |
|
|
|
|
|
|
| Cafe owner dashboard | http://171.22.25.73:3101/fa/login |
|
2026-05-29 17:02:22 +03:30
|
|
|
|
| Public Koja | http://171.22.25.73:3103/fa |
|
2026-05-28 19:06:28 +03:30
|
|
|
|
| Super-admin panel | http://171.22.25.73:3102/fa/admin/login |
|
|
|
|
|
|
| Main API (Swagger) | http://171.22.25.73:5080/swagger |
|
|
|
|
|
|
| Admin API (Swagger) | http://171.22.25.73:5081/swagger |
|
|
|
|
|
|
| Gitea | http://171.22.25.73:3000 |
|
2026-05-31 11:06:24 +03:30
|
|
|
|
| Nexus | https://mirror.soroushasadi.com/ |
|
2026-05-28 19:06:28 +03:30
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Day-to-day: pushing code
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add .
|
|
|
|
|
|
git commit -m "your message"
|
|
|
|
|
|
git push origin main # GitHub backup
|
|
|
|
|
|
git push gitea main # ← triggers CI + auto-deploy
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Every push to `main` on Gitea runs all CI jobs. If all pass, deploy runs automatically.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Checking containers on the server
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# Status of all app containers
|
|
|
|
|
|
docker compose -f docker-compose.yml -f docker-compose.admin.yml ps
|
|
|
|
|
|
|
|
|
|
|
|
# Live logs for a service
|
|
|
|
|
|
docker compose logs -f api
|
|
|
|
|
|
docker compose logs -f web
|
|
|
|
|
|
docker compose logs -f admin-api
|
|
|
|
|
|
|
|
|
|
|
|
# All containers on the machine
|
|
|
|
|
|
docker ps
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## When domain is ready (tomorrow)
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Point DNS at the server
|
|
|
|
|
|
|
|
|
|
|
|
Create these A records — all pointing to `171.22.25.73`:
|
|
|
|
|
|
|
|
|
|
|
|
| Hostname | Service |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `meezi.ir` | Marketing website |
|
|
|
|
|
|
| `app.meezi.ir` | Cafe dashboard |
|
|
|
|
|
|
| `api.meezi.ir` | Main API |
|
2026-05-29 17:02:22 +03:30
|
|
|
|
| `koja.meezi.ir` | Koja |
|
2026-05-28 19:06:28 +03:30
|
|
|
|
| `admin.meezi.ir` | Admin panel |
|
|
|
|
|
|
| `admin-api.meezi.ir` | Admin API |
|
|
|
|
|
|
|
|
|
|
|
|
### 2. Update `ENV_FILE` secret in Gitea
|
|
|
|
|
|
|
|
|
|
|
|
Remove the IP-based section and replace with:
|
|
|
|
|
|
|
|
|
|
|
|
```env
|
|
|
|
|
|
# ── Domain ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
DOMAIN=meezi.ir
|
|
|
|
|
|
ACME_EMAIL=you@example.com
|
|
|
|
|
|
|
2026-05-27 21:33:10 +03:30
|
|
|
|
NEXT_PUBLIC_API_URL=https://api.meezi.ir
|
2026-05-28 19:06:28 +03:30
|
|
|
|
NEXT_PUBLIC_ADMIN_API_URL=https://admin-api.meezi.ir
|
|
|
|
|
|
NEXT_PUBLIC_SITE_URL=https://meezi.ir
|
2026-05-29 17:02:22 +03:30
|
|
|
|
NEXT_PUBLIC_KOJA_URL=https://koja.meezi.ir
|
2026-05-28 19:06:28 +03:30
|
|
|
|
|
|
|
|
|
|
APP_QR_BASE_URL=https://app.meezi.ir
|
|
|
|
|
|
BILLING_DASHBOARD_URL=https://app.meezi.ir
|
|
|
|
|
|
|
|
|
|
|
|
CORS_ORIGIN_0=https://app.meezi.ir
|
|
|
|
|
|
CORS_ORIGIN_1=https://meezi.ir
|
2026-05-29 17:02:22 +03:30
|
|
|
|
CORS_ORIGIN_2=https://koja.meezi.ir
|
2026-05-28 19:06:28 +03:30
|
|
|
|
CORS_ADMIN_ORIGIN_0=https://admin.meezi.ir
|
|
|
|
|
|
|
|
|
|
|
|
# Remove all PORT= lines — Caddy is the only public endpoint
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. Update CI deploy steps
|
|
|
|
|
|
|
|
|
|
|
|
In `.gitea/workflows/ci-cd.yml` find the deploy job and add `docker-compose.caddy.yml`:
|
|
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
- name: Start all services
|
|
|
|
|
|
run: |
|
|
|
|
|
|
docker compose \
|
|
|
|
|
|
-f docker-compose.yml \
|
|
|
|
|
|
-f docker-compose.admin.yml \
|
|
|
|
|
|
-f docker-compose.caddy.yml \
|
|
|
|
|
|
up -d --remove-orphans
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Open port 80 and 443 in the server firewall:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
ufw allow 80
|
|
|
|
|
|
ufw allow 443
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. Push to Gitea
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git push gitea main
|
2026-05-27 21:33:10 +03:30
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
CI rebuilds all images with the new domain URLs baked in. Caddy starts and gets
|
|
|
|
|
|
Let's Encrypt certificates automatically — no certbot or manual renewal needed.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
## Secrets to rotate before going live
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
| Secret | How to generate |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `JWT_KEY` | `openssl rand -hex 32` |
|
|
|
|
|
|
| `DB_PASSWORD` | Strong random password — update both `DB_PASSWORD=` and inside `DB_CONNECTION_STRING=` |
|
|
|
|
|
|
| `ZARINPAL_MERCHANT_ID` | panel.zarinpal.com → API → MerchantID |
|
|
|
|
|
|
| `ZARINPAL_SANDBOX` | Change to `false` for live payments |
|
|
|
|
|
|
| `KAVENEGAR_API_KEY` | kavenegar.com dashboard |
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
After updating any secret in Gitea: push a commit to trigger a redeploy.
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
---
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
## Mirror server (Nexus)
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
Nexus runs separately and should always be running:
|
2026-05-27 21:33:10 +03:30
|
|
|
|
|
2026-05-28 19:06:28 +03:30
|
|
|
|
```bash
|
|
|
|
|
|
# Start (first time or after server reboot)
|
|
|
|
|
|
docker compose -f docker-compose.mirror.yml up -d
|
|
|
|
|
|
|
2026-05-31 11:06:24 +03:30
|
|
|
|
# Health check (on server or via domain)
|
|
|
|
|
|
curl -s https://mirror.soroushasadi.com/service/rest/v1/status
|
2026-05-28 19:06:28 +03:30
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Provisioned repos:
|
|
|
|
|
|
|
|
|
|
|
|
| Repo | Type | Upstream |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `nuget-group` | NuGet group | Liara → Runflare fallback |
|
|
|
|
|
|
| `npm-group` | npm group | Liara → Runflare fallback |
|
|
|
|
|
|
| `docker-hub-proxy` | Docker proxy :5000 | Docker Hub |
|
|
|
|
|
|
| `mcr-proxy` | Docker proxy :5002 | mcr.microsoft.com |
|
|
|
|
|
|
| `pypi-proxy` | PyPI proxy | Liara |
|
|
|
|
|
|
| `ubuntu-proxy` | APT proxy | Liara (jammy) |
|
|
|
|
|
|
| `ubuntu-security-proxy` | APT proxy | Liara (jammy-security) |
|
|
|
|
|
|
|
|
|
|
|
|
To add new mirrors or switch upstreams:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./mirrors/nexus/add-liara-mirrors.sh
|
|
|
|
|
|
./mirrors/nexus/update-docker-upstream.sh
|
|
|
|
|
|
```
|