d05b329c7a
- Central nginx is containerized and proxies via host IP (171.22.25.73:port), not localhost → publish app on host :2569 (was 127.0.0.1)
- nginx vhost rewritten to match the monolithic config style (server blocks to paste into http{}, manual /etc/ssl/hamkadr certs, proxy_pass 171.22.25.73:2569, $connection_upgrade)
- DEPLOY.md: corrected architecture/ports, removed certbot+sites-available (use manual certs + single nginx.conf)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
5.3 KiB
Markdown
120 lines
5.3 KiB
Markdown
# Deploying همکادر / hamkadr.ir
|
|
|
|
CI/CD via the **soroush method**: push to Gitea → Gitea Actions builds (through the Nexus mirror)
|
|
and the self-hosted runner deploys with Docker Compose. nginx (already on the server) terminates
|
|
TLS for `hamkadr.ir` and reverse-proxies to the app.
|
|
|
|
## Architecture & open ports
|
|
|
|
```
|
|
Internet ─443/80─► central nginx (container) ─► http://171.22.25.73:2569 ─► hamkadr_api (container :8080)
|
|
(nexus/mirror/gitea/meezi…) host-published port │ internal docker net
|
|
▼
|
|
hamkadr_db (postgres, no host port)
|
|
```
|
|
|
|
Matches the existing soroush pattern: the **containerized** central nginx reaches each app via the
|
|
**host IP + published port** (`171.22.25.73:<port>`), not localhost. So hamkadr publishes `2569` on
|
|
the host (like meezi 5080, draletaha 5010…).
|
|
|
|
| Port | Open? | Purpose |
|
|
|------|-------|---------|
|
|
| 22 | ✅ (ideally IP-restricted) | SSH |
|
|
| 80 / 443 | ✅ (already open) | central nginx — serves `hamkadr.ir` too |
|
|
| 2569 | host-published | app; only nginx proxies to it. Optionally firewall to the nginx host. |
|
|
| 5432 | ❌ internal docker net only | Postgres — never published |
|
|
|
|
No firewall change needed for 80/443 (nginx already serves git./mirror./meezi). 2569 is published
|
|
on the host like the other apps.
|
|
|
|
## Files in this repo
|
|
|
|
| File | Role |
|
|
|------|------|
|
|
| `Dockerfile` | multi-stage build, images + NuGet via `mirror.soroushasadi.com` |
|
|
| `nuget.docker.config` | NuGet → Nexus `nuget-group` |
|
|
| `docker-compose.yml` | production stack: `api` (host :${HOST_PORT}) + `db` (internal) + named volume |
|
|
| `docker-compose.dev.yml` | local-dev Postgres only (host 5433) for `dotnet run` |
|
|
| `.gitea/workflows/ci-cd.yml` | build job + self-hosted deploy (backup → rollback tag → recreate → health-wait) |
|
|
| `deploy/nginx-hamkadr.ir.conf` | nginx vhost for hamkadr.ir |
|
|
|
|
## One-time setup
|
|
|
|
### 1. DNS
|
|
A records → 171.22.25.73:
|
|
```
|
|
hamkadr.ir A 171.22.25.73
|
|
www.hamkadr.ir A 171.22.25.73
|
|
```
|
|
|
|
### 2. Gitea runner
|
|
Confirm the `act_runner` on this server has the **`self-hosted:host`** label (the deploy job needs it)
|
|
and its user is in the `docker` group. (Already true if other soroush projects deploy here.)
|
|
|
|
### 3. ENV_FILE secret
|
|
Set at `https://git.soroushasadi.com/soroushdes/hamkadr/settings/secrets` → key **`ENV_FILE`**:
|
|
|
|
`docker-compose.yml` substitutes these into the `api`/`db` services (it derives
|
|
`ConnectionStrings__Default` and `Auth__AdminPhone` for you), so the secret is just:
|
|
|
|
```dotenv
|
|
# host port nginx proxies to (must match deploy/nginx-hamkadr.ir.conf)
|
|
HOST_PORT=2569
|
|
|
|
# Postgres — generate a strong password: openssl rand -hex 24
|
|
POSTGRES_DB=hamkadr
|
|
POSTGRES_USER=hamkadr
|
|
POSTGRES_PASSWORD=__CHANGE_ME__
|
|
|
|
# Platform admin phone (gets the Admin role on login)
|
|
ADMIN_PHONE=09XXXXXXXXX
|
|
```
|
|
> **That's the whole secret.** Everything else — the **AI audit layer** *and* the **channel
|
|
> sources** (Telegram channels, Bale bot token, Divar queries, auto-ingest on/off + interval) — is
|
|
> configured at runtime in the admin panel (`/Admin/Settings`), stored in the DB. No redeploy to
|
|
> change them. Defaults: AI off, mode = Manual, all sources off ⇒ nothing publishes without admin
|
|
> review.
|
|
> `ASPNETCORE_ENVIRONMENT=Production` is set by the compose file ⇒ only **reference data**
|
|
> (roles/cities/districts) is seeded — no demo facilities/shifts.
|
|
|
|
### 4. TLS cert + nginx vhost
|
|
Your central nginx is a **single monolithic `nginx.conf`** with **manually-placed certs** (no
|
|
certbot). Match that:
|
|
|
|
1. Put the hamkadr.ir cert where nginx expects (same convention as your other domains):
|
|
```
|
|
/etc/ssl/hamkadr/fullchain.pem
|
|
/etc/ssl/hamkadr/privateKey.pem
|
|
```
|
|
2. Paste the two `server { }` blocks from `deploy/nginx-hamkadr.ir.conf` **into the `http { }`
|
|
block** of your central nginx.conf (next to meezi/draletaha). They proxy to
|
|
`http://171.22.25.73:2569` and reuse the global `$connection_upgrade` map.
|
|
3. Reload:
|
|
```bash
|
|
nginx -t && nginx -s reload # or: docker exec <nginx-container> nginx -s reload
|
|
```
|
|
|
|
### 5. First deploy
|
|
```bash
|
|
git push gitea main # add the gitea remote first if needed
|
|
```
|
|
Watch `https://git.soroushasadi.com/soroushdes/hamkadr/actions`. The app auto-applies EF migrations
|
|
on startup and seeds reference data; nginx already proxies hamkadr.ir to it.
|
|
|
|
## Operations
|
|
|
|
- **Backups:** every deploy runs `pg_dump` → `/opt/hamkadr-backups/hamkadr-<timestamp>.sql` before touching containers.
|
|
- **Rollback:** the previous image is tagged `mirror.soroushasadi.com/hamkadr/api:rollback` each deploy:
|
|
```bash
|
|
docker stop hamkadr_api && docker rm hamkadr_api
|
|
API_TAG=rollback docker compose up -d --no-deps api
|
|
```
|
|
- **Rotate a secret:** edit `ENV_FILE` in Gitea, push any commit to redeploy.
|
|
- **Logs:** `docker logs -f hamkadr_api`
|
|
- **Restore a backup:** `cat /opt/hamkadr-backups/<file>.sql | docker exec -i hamkadr_db psql -U hamkadr -d hamkadr`
|
|
|
|
## Safety (never do these)
|
|
- ❌ `docker compose down -v` — deletes the database volume.
|
|
- ❌ bare `docker compose down` / `restart` — would stop other projects on the shared host. The
|
|
workflow always uses `--no-deps <service>` and explicit `stop`/`rm`.
|