Files
hamkadr/.gitea/workflows/ci-cd.yml
T
soroush.asadi 69a630d185 CI/Docker NuGet: Nexus-only (drop Liara fallback)
NuGet loads the service index of EVERY listed source, so a 500 from the
Liara fallback aborted the whole restore (NU1301) even though Nexus was
healthy. Mirror cert chain is fixed now, so use our Nexus mirror as the
single source of truth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:10:56 +03:30

172 lines
7.2 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 only)
# Single source = our Nexus mirror. We do NOT list Liara as a fallback: NuGet loads
# the service index of EVERY configured source, so a 500 from a fallback aborts the
# whole restore (NU1301). Nexus is the source of truth.
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>
<config>
<add key="http_retry_count" value="6" />
<add key="http_retry_delay_milliseconds" value="1000" />
</config>
</configuration>
EOF
- name: Restore
# NuGetAudit=false: the audit pings api.nuget.org for CVE data, which is
# filtered in Iran (100s timeout + NU1900 noise). The mirror has the packages.
run: dotnet restore src/JobsMedical.Web/JobsMedical.Web.csproj --configfile /tmp/nuget.ci.config -p:NuGetAudit=false
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
- name: Build
run: dotnet build src/JobsMedical.Web/JobsMedical.Web.csproj --no-restore -c Release -p:NuGetAudit=false
# ── 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