Files
hamkadr/DEPLOY.md
T
soroush.asadi 36bb165438 Real channel fetch (Telegram/Bale/Divar) + AI-audited automation engine + CI/CD
- 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>
2026-06-03 17:41:02 +03:30

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_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

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>.sql before touching containers.
  • Rollback: the previous image is tagged hamkadr-app:rollback each 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_FILE in 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 explicit stop/rm.