Files
meezi/DEPLOY.md
T
soroush.asadi 34040503cf docs: rewrite DEPLOY.md with self-hosted setup guide
Replaces outdated Arvan Cloud instructions with the current
self-hosted stack: Gitea CI, Nexus mirror, Docker Compose,
IP-based access today and Caddy+domain tomorrow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:06:28 +03:30

279 lines
8.8 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.
# Meezi — Deployment Guide
## Architecture overview
```
Server: 171.22.25.73
├── Gitea :3000 ← source control + CI runner
├── Nexus :8081 ← package mirror (NuGet, npm, Docker)
├── 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
├── meezi-finder :3103 ← Next.js public finder
├── meezi-db :5434 ← PostgreSQL (not internet-facing)
└── meezi-redis :6381 ← Redis (not internet-facing)
```
Docker Compose files:
```
docker-compose.yml main services (postgres, redis, api, web, website, finder)
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 ────────────────────────────────────────────────────────────────
RUN_MIGRATIONS=true
# ── 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
NEXT_PUBLIC_FINDER_URL=http://171.22.25.73:3103
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
FINDER_PORT=3103
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
```
> 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
On the server:
```bash
cd ~/meezi
git pull origin main
git push gitea main
```
Watch the pipeline:
```
http://171.22.25.73:3000/soroushdes/meezi/actions
```
CI takes ~510 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 |
| Public finder | http://171.22.25.73:3103/fa |
| 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 |
| Nexus | http://171.22.25.73:8081 |
---
## 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 |
| `finder.meezi.ir` | Finder |
| `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
NEXT_PUBLIC_API_URL=https://api.meezi.ir
NEXT_PUBLIC_ADMIN_API_URL=https://admin-api.meezi.ir
NEXT_PUBLIC_SITE_URL=https://meezi.ir
NEXT_PUBLIC_FINDER_URL=https://finder.meezi.ir
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
CORS_ORIGIN_2=https://finder.meezi.ir
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
```
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.
---
## Secrets to rotate before going live
| 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 |
After updating any secret in Gitea: push a commit to trigger a redeploy.
---
## Mirror server (Nexus)
Nexus runs separately and should always be running:
```bash
# Start (first time or after server reboot)
docker compose -f docker-compose.mirror.yml up -d
# Health check
curl -s http://localhost:8081/service/rest/v1/status
```
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
```