Files
hamkadr/.gitea/workflows/ci-cd.yml
T
soroush.asadi 943c3b7b3c
CI/CD / CI · dotnet build (push) Successful in 51s
CI/CD / Deploy · hamkadr (push) Successful in 57s
[CI] Fix NuGet restore: use Liara mirror (Nexus TLS chain incomplete)
mirror.soroushasadi.com serves a leaf-only TLS chain (no intermediate).
.NET on Linux does not auto-fetch the intermediate via AIA like Windows
does, so CI/Docker restores fail with NU1301 PartialChain. Switch the
Linux build configs (CI inline config + nuget.docker.config) to the
Liara mirror, which serves a complete chain. Also disable NuGetAudit to
avoid the api.nuget.org (filtered) 100s timeout + NU1900 noise.

Local dev nuget.config keeps Nexus primary (Windows resolves the chain).
Re-add Nexus to the Linux configs once nginx serves fullchain.pem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:36:00 +03:30

174 lines
7.3 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 (Liara primary; Nexus optional)
# NOTE: mirror.soroushasadi.com currently serves an incomplete TLS chain
# (leaf only, no intermediate). .NET on Linux does NOT auto-fetch the
# intermediate via AIA the way Windows does, so it fails with PartialChain.
# Liara serves a complete chain, so it is the deterministic source here.
# Re-add Nexus once nginx points ssl_certificate at fullchain.pem.
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="liara"
value="https://package-mirror.liara.ir/repository/nuget/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