# Meezi — Deployment Guide ## Architecture overview ``` Server: 171.22.25.73 │ ├── Gitea :3000 ← source control + CI runner ├── Nexus mirror.soroushasadi.com ← package mirror (NuGet, npm, Docker, MCR) │ ├── 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-koja :3103 ← Next.js public discovery (Koja) ├── 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, koja) 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_KOJA_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 KOJA_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 ~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 | | Public Koja | 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 | https://mirror.soroushasadi.com/ | --- ## 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 | | `koja.meezi.ir` | Koja | | `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_KOJA_URL=https://koja.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://koja.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 (on server or via domain) curl -s https://mirror.soroushasadi.com/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 ```