36612b6bf0
Both the CI restore (/tmp/nuget.ci.config) and the Docker image build (nuget.docker.config) now use https://mirror.soroushasadi.com/repository/ nuget-group/ as the primary source with Liara as fallback, so a single mirror returning 500 no longer breaks restore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
174 lines
7.3 KiB
YAML
174 lines
7.3 KiB
YAML
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 group primary; Liara fallback)
|
||
# Nexus nuget-group is the primary mirror; Liara is kept as a fallback so a
|
||
# single mirror outage (e.g. a 500 on the service index) doesn't break restore.
|
||
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" />
|
||
<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
|