name: Agent CI/CD on: pull_request: push: branches: - main - master tags: - "v*" - "latest" jobs: validate: runs-on: ubuntu-22.04 permissions: contents: read defaults: run: shell: bash steps: - name: Checkout code env: SERVER_URL: ${{ github.server_url }} REPOSITORY: ${{ github.repository }} COMMIT_SHA: ${{ github.sha }} GIT_USERNAME: ${{ github.actor }} GIT_TOKEN: ${{ github.token }} run: | case "$SERVER_URL" in http://*) AUTH_SERVER_URL="http://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#http://}" ;; https://*) AUTH_SERVER_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#https://}" ;; *) AUTH_SERVER_URL="$SERVER_URL" ;; esac if [ ! -d .git ]; then git init . fi if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin "${AUTH_SERVER_URL}/${REPOSITORY}.git" else git remote add origin "${AUTH_SERVER_URL}/${REPOSITORY}.git" fi git fetch --depth=1 origin "$COMMIT_SHA" git checkout --force --detach FETCH_HEAD git clean -ffdx - name: Install Bun run: | case "$(uname -m)" in x86_64) BUN_ARCH="x64" ;; aarch64|arm64) BUN_ARCH="aarch64" ;; *) echo "Unsupported architecture: $(uname -m)" exit 1 ;; esac BUN_ASSET="bun-linux-${BUN_ARCH}.zip" BUN_MIRROR_URL="https://ghproxy.net/https://github.com/oven-sh/bun/releases/latest/download/${BUN_ASSET}" curl -fL --retry 3 --retry-delay 5 "$BUN_MIRROR_URL" -o /tmp/bun.zip rm -rf "$HOME/.bun" mkdir -p "$HOME/.bun/bin" unzip -qo /tmp/bun.zip -d /tmp install -m 0755 "/tmp/bun-linux-${BUN_ARCH}/bun" "$HOME/.bun/bin/bun" echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - name: Install dependencies run: bun install --frozen-lockfile - name: Run checks run: bun run check docker-image: runs-on: ubuntu-22.04 needs: validate if: startsWith(github.ref, 'refs/tags/') permissions: contents: read defaults: run: shell: bash steps: - name: Checkout code env: SERVER_URL: ${{ github.server_url }} REPOSITORY: ${{ github.repository }} COMMIT_SHA: ${{ github.sha }} GIT_USERNAME: ${{ github.actor }} GIT_TOKEN: ${{ github.token }} run: | case "$SERVER_URL" in http://*) AUTH_SERVER_URL="http://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#http://}" ;; https://*) AUTH_SERVER_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#https://}" ;; *) AUTH_SERVER_URL="$SERVER_URL" ;; esac if [ ! -d .git ]; then git init . fi if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin "${AUTH_SERVER_URL}/${REPOSITORY}.git" else git remote add origin "${AUTH_SERVER_URL}/${REPOSITORY}.git" fi git fetch --depth=1 origin "$COMMIT_SHA" git checkout --force --detach FETCH_HEAD git clean -ffdx - name: Normalize image metadata env: RAW_REGISTRY_HOST: ${{ vars.REGISTRY_HOST }} RAW_REPOSITORY: ${{ github.repository }} RAW_REF: ${{ github.ref }} RAW_REF_NAME: ${{ github.ref_name }} run: | REGISTRY_HOST="${RAW_REGISTRY_HOST#http://}" REGISTRY_HOST="${REGISTRY_HOST#https://}" REGISTRY_HOST="${REGISTRY_HOST%/}" REPOSITORY_PATH="${RAW_REPOSITORY#/}" IMAGE_REPOSITORY_PATH="$(printf '%s' "$REPOSITORY_PATH" | tr '[:upper:]' '[:lower:]')" IMAGE_NAME="${REGISTRY_HOST}/${IMAGE_REPOSITORY_PATH}" IMAGE_TAG="${RAW_REF_NAME}" { echo "REGISTRY_HOST=${REGISTRY_HOST}" echo "REPOSITORY_PATH=${REPOSITORY_PATH}" echo "IMAGE_REPOSITORY_PATH=${IMAGE_REPOSITORY_PATH}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "IMAGE_TAG=${IMAGE_TAG}" echo "IMAGE_REF=${IMAGE_NAME}:${IMAGE_TAG}" } >> "$GITHUB_ENV" - name: Login to Gitea Container Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "$REGISTRY_HOST" \ --username "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin - name: Build and Push Image run: | push_with_retry() { image_ref="$1" attempt=1 max_attempts=3 while [ "$attempt" -le "$max_attempts" ]; do if docker push "$image_ref"; then return 0 fi if [ "$attempt" -eq "$max_attempts" ]; then return 1 fi echo "Push failed for $image_ref (attempt $attempt/$max_attempts); retrying in 10s..." attempt=$((attempt + 1)) sleep 10 done } if [ "${IMAGE_TAG}" = "latest" ]; then docker build \ -f ./Dockerfile \ -t "${IMAGE_NAME}:latest" \ . push_with_retry "${IMAGE_NAME}:latest" else docker build \ -f ./Dockerfile \ -t "${IMAGE_NAME}:${IMAGE_TAG}" \ -t "${IMAGE_NAME}:latest" \ . push_with_retry "${IMAGE_NAME}:${IMAGE_TAG}" push_with_retry "${IMAGE_NAME}:latest" fi - name: Notify Deploy Server run: | post_deploy_webhook() { label="$1" payload="$2" http_code=$(curl -sS -D /tmp/deploy_headers.txt -o /tmp/deploy_response.txt -w "%{http_code}" -X POST "${{ vars.DEPLOY_WEBHOOK_URL }}" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${{ secrets.DEPLOY_WEBHOOK_TOKEN }}" \ -d "$payload") echo "[$label] webhook HTTP status: ${http_code}" if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then return 0 fi echo "[$label] response headers:" cat /tmp/deploy_headers.txt echo "[$label] response body:" cat /tmp/deploy_response.txt return 1 } PRIMARY_PAYLOAD="{\"image\":\"${IMAGE_REF}\",\"tag\":\"${IMAGE_TAG}\",\"repo\":\"${REPOSITORY_PATH}\"}" FALLBACK_PAYLOAD="{\"image\":\"${IMAGE_REF}\",\"tag\":\"${IMAGE_TAG}\",\"repo\":\"${IMAGE_REPOSITORY_PATH}\"}" echo "Deploy webhook target: ${{ vars.DEPLOY_WEBHOOK_URL }}" echo "Deploy payload(primary): image=${IMAGE_REF}, tag=${IMAGE_TAG}, repo=${REPOSITORY_PATH}" if post_deploy_webhook "primary" "$PRIMARY_PAYLOAD"; then exit 0 fi echo "Primary webhook request failed, retrying with lowercase repo path..." echo "Deploy payload(fallback): image=${IMAGE_REF}, tag=${IMAGE_TAG}, repo=${IMAGE_REPOSITORY_PATH}" if post_deploy_webhook "fallback" "$FALLBACK_PAYLOAD"; then exit 0 fi echo "Deploy webhook failed after primary and fallback attempts." exit 1 deploy-fallback-log: runs-on: ubuntu-22.04 needs: docker-image if: failure() steps: - name: Deployment not triggered run: echo "Image build/push failed, deployment webhook was not called."