Files
hamkadr/.gitea/workflows/ci-cd.yml
T
soroush.asadi 8f5d926d42
CI/CD / CI · dotnet build (push) Successful in 2m58s
CI/CD / Deploy · hamkadr (push) Failing after 6m23s
Align CI/CD with soroush method (DrSousan single-app pattern)
Audited against working Meezi/DrSousan pipelines. Fixes:
- Single docker-compose.yml is the production stack (api + internal db); folded in docker-compose.prod.yml; dev Postgres → docker-compose.dev.yml
- Dockerfile HEALTHCHECK (bash /dev/tcp) so deploy's docker-inspect Health.Status wait works
- Naming to convention: service api, container hamkadr_api/hamkadr_db, image mirror.soroushasadi.com/hamkadr/api:${API_TAG}
- Workflow rewritten to DrSousan pattern: ci build + deploy (rollback-tag before build, pg_dump backup, stop/rm/up, docker-inspect health-wait with crash detection, scoped image prune)
- environment: block with ${VAR:-default} substitution (no hard-failing env_file); HOST_PORT; .env excluded from image context
- nginx vhost + DEPLOY.md updated to match

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:38:22 +03:30

163 lines
6.6 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: hamkadr-cicd-${{ github.ref }}
cancel-in-progress: true
# ─────────────────────────────────────────────────────────────────────────────
# Runner labels (act_runner):
# ubuntu-latest:docker://node:20-alpine ← CI jobs run in real Docker containers
# self-hosted:host ← deploy runs directly on the server
# All images/packages via Nexus at mirror.soroushasadi.com.
# Required Gitea secret: ENV_FILE (contents of .env)
# ─────────────────────────────────────────────────────────────────────────────
jobs:
# ── CI: compile-check (every push / PR) ──────────────────────────────────────
ci:
name: "CI · dotnet build"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/dotnet/sdk:10.0
options: --add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write NuGet config (Nexus)
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3" />
</packageSources>
</configuration>
EOF
- name: Restore
run: dotnet restore src/JobsMedical.Web/JobsMedical.Web.csproj --configfile /tmp/nuget.ci.config
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
- name: Build
run: dotnet build src/JobsMedical.Web/JobsMedical.Web.csproj --no-restore -c Release
# ── CD: build image → deploy on the server (push to main only) ────────────────
deploy:
name: "Deploy · hamkadr"
runs-on: self-hosted
env:
# act host runner starts with a minimal PATH — extend so docker/snap resolve.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
needs: [ci]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
timeout-minutes: 30
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout FETCH_HEAD
- name: Write .env
run: printf '%s' "$ENV_FILE" > .env
env:
ENV_FILE: ${{ secrets.ENV_FILE }}
- name: Tag current image for rollback (before rebuild)
run: |
if docker image inspect mirror.soroushasadi.com/hamkadr/api:latest >/dev/null 2>&1; then
docker tag mirror.soroushasadi.com/hamkadr/api:latest mirror.soroushasadi.com/hamkadr/api:rollback
echo "Tagged previous image → :rollback"
else
echo "No previous image — first deploy."
fi
- name: Back up database (if running)
run: |
set -a; . ./.env; set +a
if docker ps -q --filter name='^hamkadr_db$' | grep -q .; then
BACKUP_DIR="/opt/hamkadr-backups"; mkdir -p "$BACKUP_DIR"
STAMP=$(date +%Y%m%d-%H%M%S)
docker exec hamkadr_db pg_dump -U "${POSTGRES_USER:-hamkadr}" "${POSTGRES_DB:-hamkadr}" \
> "$BACKUP_DIR/hamkadr-$STAMP.sql" \
&& echo "✅ DB backed up → $BACKUP_DIR/hamkadr-$STAMP.sql" \
|| echo "⚠️ backup failed (non-fatal)"
ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | tail -n +11 | xargs -r rm
else
echo "️ hamkadr_db not running — first deploy, nothing to back up."
fi
- name: Build image
run: docker compose build api
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Start database
run: docker compose up -d --no-deps db
- name: Deploy app (stop + rm + up — reliable across docker versions)
run: |
docker stop hamkadr_api 2>/dev/null || true
docker rm hamkadr_api 2>/dev/null || true
docker compose up -d --no-deps api
- name: Wait for healthy
run: |
echo "Waiting for hamkadr_api (up to 3 min)..."
for i in $(seq 1 36); do
HEALTH=$(docker inspect --format='{{.State.Health.Status}}' hamkadr_api 2>/dev/null || echo "missing")
STATE=$(docker inspect --format='{{.State.Status}}' hamkadr_api 2>/dev/null || echo "missing")
RESTARTS=$(docker inspect --format='{{.RestartCount}}' hamkadr_api 2>/dev/null || echo "0")
echo " [$i/36] state=$STATE health=$HEALTH restarts=$RESTARTS"
[ "$HEALTH" = "healthy" ] && echo "✅ hamkadr_api healthy" && exit 0
if [ "$STATE" = "exited" ] || [ "$STATE" = "dead" ]; then
echo "❌ hamkadr_api crashed — logs:"; docker logs hamkadr_api 2>&1 | tail -120; exit 1
fi
if [ "$RESTARTS" -gt 1 ]; then
echo "❌ hamkadr_api crash-loop — logs:"; docker logs hamkadr_api 2>&1 | tail -120; exit 1
fi
[ "$i" = "36" ] && echo "❌ timeout" && docker logs hamkadr_api 2>&1 | tail -80 && exit 1
sleep 5
done
- name: Show containers
if: always()
run: docker compose ps
- name: Prune old hamkadr images
if: success()
# Only untagged (<none>) hamkadr images — never touches other projects.
run: |
docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' \
| grep '^mirror\.soroushasadi\.com/hamkadr/' \
| grep '<none>' \
| awk '{print $2}' \
| xargs -r docker rmi || true