# 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──► nginx (host, existing) ──► 127.0.0.1:8090 ──► hamkadr-app (container :8080) │ internal docker net ▼ hamkadr-db (postgres, no host port) ``` | Port | Open? | Purpose | |------|-------|---------| | 22 | ✅ (ideally IP-restricted) | SSH | | 80 | ✅ | HTTP → 443 redirect + Let's Encrypt ACME | | 443 | ✅ | HTTPS `hamkadr.ir` | | 8090 | ❌ host-localhost only | app, reached only by nginx | | 5432 | ❌ internal docker net only | Postgres — never published | `ufw` should be exactly: `allow 22, 80, 443`. Nothing else. (80/443 are already open since nginx serves git./mirror. — no firewall change needed.) ## 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.prod.yml` | `app` (127.0.0.1:${APP_PORT}) + `db` (internal) + named volume | | `.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 → server IP: ``` hamkadr.ir A www.hamkadr.ir A ``` ### 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`**: ```dotenv ASPNETCORE_ENVIRONMENT=Production ASPNETCORE_URLS=http://+:8080 # host port nginx proxies to (must match deploy/nginx-hamkadr.ir.conf) APP_PORT=8090 # Postgres (container) — generate a strong password: openssl rand -hex 24 POSTGRES_DB=hamkadr POSTGRES_USER=hamkadr POSTGRES_PASSWORD=__CHANGE_ME__ # EF Core connection string (host = compose service name "db") ConnectionStrings__Default=Host=db;Port=5432;Database=hamkadr;Username=hamkadr;Password=__CHANGE_ME__ # Platform admin (the phone that gets the Admin role on login) Auth__AdminPhone=09XXXXXXXXX # Future: Kavenegar / SMS.ir keys for real OTP delivery # --- Channel scraping (optional; off by default) --- # Enable the background worker and the sources you want, then their fetch runs on a timer. # Ingestion__Enabled=true # Ingestion__IntervalMinutes=30 # Telegram (public channels via t.me/s — no token needed): # Ingestion__Telegram__Enabled=true # Ingestion__Telegram__Channels__0=shift_channel_username # Ingestion__Telegram__Channels__1=another_channel # Bale (bot must be a member of the channel; Telegram-style Bot API): # Ingestion__Bale__Enabled=true # Ingestion__Bale__BotToken=__BALE_BOT_TOKEN__ # Divar (best-effort web-search): # Ingestion__Divar__Enabled=true # Ingestion__Divar__Queries__0=استخدام پزشک # Ingestion__Divar__Queries__1=پرستار ``` > The **AI audit layer** is configured at runtime in the admin panel (`/Admin/Settings`) — endpoint, > model, API key, prompt/framework, and auto-approve — not via env. Default: AI off, mode = Manual, > so every ingested listing waits in the review queue until an admin publishes it. > `POSTGRES_PASSWORD` and the password in `ConnectionStrings__Default` must be identical. > `ASPNETCORE_ENVIRONMENT=Production` ⇒ only **reference data** (roles/cities/districts) is seeded — > no demo facilities/shifts. Real employers add listings via the employer panel. ### 4. nginx vhost + TLS ```bash sudo cp deploy/nginx-hamkadr.ir.conf /etc/nginx/sites-available/hamkadr.ir sudo ln -s /etc/nginx/sites-available/hamkadr.ir /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo certbot --nginx -d hamkadr.ir -d www.hamkadr.ir ``` ### 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-.sql` before touching containers. - **Rollback:** the previous image is tagged `hamkadr-app:rollback` each deploy: ```bash docker stop hamkadr-app && docker rm hamkadr-app docker run -d --name hamkadr-app --env-file .env --network hamkadr_default \ -p 127.0.0.1:8090:8080 hamkadr-app:rollback ``` - **Rotate a secret:** edit `ENV_FILE` in Gitea, push any commit to redeploy. - **Logs:** `docker logs -f hamkadr-app` - **Restore a backup:** `cat /opt/hamkadr-backups/.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 ` and explicit `stop`/`rm`.