- Fetch: Telegram via t.me/s, Bale via Bot API, Divar via web-search (HttpClient, config-gated, graceful) - AI layer: DB-backed AppSetting (mode auto/manual, thresholds, AI endpoint/model/key/prompt/framework, auto-approve); OpenAI-compatible IAiAuditor (self-host/Iranian endpoints; fails safe to manual) - Pipeline: fetch → dedupe(hash) → parse → validate → AI audit → Discard/Flag/Queue/auto-publish (resolve-or-create facility) - Admin: /Admin/Settings automation+AI panel; queue shows confidence + AI verdict; flagged section - CI/CD: Dockerfile, docker-compose.prod.yml, .gitea/workflows/ci-cd.yml, nginx vhost, DEPLOY.md; forwarded headers + /healthz + prod reference-only seed; ports 22/80/443 only Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.4 KiB
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 <server-ip>
www.hamkadr.ir A <server-ip>
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:
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_PASSWORDand the password inConnectionStrings__Defaultmust 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
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
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>.sqlbefore touching containers. - Rollback: the previous image is tagged
hamkadr-app:rollbackeach deploy: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_FILEin Gitea, push any commit to redeploy. - Logs:
docker logs -f hamkadr-app - 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 explicitstop/rm.