Compare commits

...

3 Commits

Author SHA1 Message Date
XiaoXia Bot ea32481e66 Merge remote-tracking branch 'origin/develop' into ci/matrix-build-parallelism
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 37s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m56s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m2s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m40s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m54s
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m21s
2026-07-15 14:08:16 +08:00
XiaoXia Bot d7b7c0b235 fix(ci): 修复matrix重构中重复的if条件导致YAML解析失败
CI/CD Pipeline / Frontend Lint (push) Successful in 54s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 39s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 2m5s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m3s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m30s
CI/CD Pipeline / Integration Tests (push) Successful in 1m25s
2026-07-15 13:51:50 +08:00
XiaoXia Bot d120785b16 feat(ci): M-3 三镜像matrix并行构建重构\n\n将6个独立构建job(3 staging + 3 production)重构为2个matrix job\n- 代码量减少249行(21%),维护性大幅提升\n- API/Worker/Web三服务共用同一套构建逻辑\n- web服务npm build步骤通过matrix条件控制\n- worker构建40分钟超时通过matrix.timeout配置\n- deploy-staging/deploy-production needs同步更新为单个matrix job\n- fail-fast: false,单个服务构建失败不影响其他服务 2026-07-15 13:31:53 +08:00
+67 -317
View File
@@ -403,164 +403,38 @@ jobs:
NOTIFY_MODE=failure JOB_NAME="Frontend Lint" python3 scripts/ci_notify.py
'
build-staging-api:
name: Build Staging API Image
build-staging:
name: Build Staging ${{ matrix.service_display }} Image
runs-on:
- saas
- build-farm
timeout-minutes: 30
needs:
- validate
- frontend-lint
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: "set -eu\npython3 - <<'INNERPY'\nimport io, os, tarfile, time, urllib.request, urllib.error\nurl = f\"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz\"\nrequest = urllib.request.Request(url, headers={\"Authorization\": f\"token {os.environ['GITHUB_TOKEN']}\"})\nlast_err = None\nfor attempt in range(5):\n try:\n with urllib.request.urlopen(request, timeout=120) as response:\n archive = response.read()\n break\n except urllib.error.HTTPError as e:\n last_err = e\n if e.code >= 500 and attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...\")\n time.sleep(wait)\n continue\n raise\n except Exception as e:\n last_err = e\n if attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...\"\
)\n time.sleep(wait)\n continue\nelse:\n raise last_err\nwith tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:\n root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'\n for member in tar.getmembers():\n name = member.name\n if name == root_prefix[:-1]:\n continue\n if name.startswith(root_prefix):\n member.name = name[len(root_prefix):]\n if member.name:\n tar.extract(member, '.')\nINNERPY\n"
- name: Record job start time
shell: sh
run: 'set -eu
echo "JOB_START_TIME=$(date +%s)" >> $GITHUB_ENV
echo "Job started at $(date)"
'
- name: Docker login to Registry
shell: sh
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: 'set -eu
printf ''%s'' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin
echo "Docker login successful"
'
- name: Setup cache strategy
shell: sh
run: "set -eu\n# develop/main 分支写回缓存,其他分支只读\nif [ \"${GITHUB_REF_NAME}\" = \"develop\" ] || [ \"${GITHUB_REF_NAME}\" = \"main\" ]; then\n echo \"CACHE_MODE=read-write\" >> $GITHUB_ENV\n echo \"Cache mode: read-write (will push cache)\"\nelse\n echo \"CACHE_MODE=read-only\" >> $GITHUB_ENV\n echo \"Cache mode: read-only\"\nfi\n"
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push API image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/api.Dockerfile \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"API image pushed: ${IMAGE_TAG}\"
"
- name: Job duration summary
if: always()
shell: sh
run: "set +eu\nif [ -n \"$JOB_START_TIME\" ]; then\n END_TIME=$(date +%s)\n DURATION=$((END_TIME - JOB_START_TIME))\n MINS=$((DURATION / 60))\n SECS=$((DURATION % 60))\n echo \"JOB_DURATION_SECONDS=$DURATION\" >> $GITHUB_ENV\n echo \"=== Job Duration: ${MINS}m${SECS}s ===\"\nelse\n echo \"JOB_DURATION_SECONDS=0\" >> $GITHUB_ENV\n echo \"=== Job Duration: unknown ===\"\nfi\n"
- name: Notify on failure
continue-on-error: true
if: failure()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Staging API Image" python3 scripts/ci_notify.py
'
build-staging-worker:
name: Build Staging Worker Image
runs-on:
- saas
- build-farm
timeout-minutes: 40
needs:
- validate
- frontend-lint
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: "set -eu\npython3 - <<'INNERPY'\nimport io, os, tarfile, time, urllib.request, urllib.error\nurl = f\"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz\"\nrequest = urllib.request.Request(url, headers={\"Authorization\": f\"token {os.environ['GITHUB_TOKEN']}\"})\nlast_err = None\nfor attempt in range(5):\n try:\n with urllib.request.urlopen(request, timeout=120) as response:\n archive = response.read()\n break\n except urllib.error.HTTPError as e:\n last_err = e\n if e.code >= 500 and attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...\")\n time.sleep(wait)\n continue\n raise\n except Exception as e:\n last_err = e\n if attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...\"\
)\n time.sleep(wait)\n continue\nelse:\n raise last_err\nwith tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:\n root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'\n for member in tar.getmembers():\n name = member.name\n if name == root_prefix[:-1]:\n continue\n if name.startswith(root_prefix):\n member.name = name[len(root_prefix):]\n if member.name:\n tar.extract(member, '.')\nINNERPY\n"
- name: Record job start time
shell: sh
run: 'set -eu
echo "JOB_START_TIME=$(date +%s)" >> $GITHUB_ENV
echo "Job started at $(date)"
'
- name: Docker login to Registry
shell: sh
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: 'set -eu
printf ''%s'' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin
echo "Docker login successful"
'
- name: Setup cache strategy
shell: sh
run: "set -eu\n# develop/main 分支写回缓存,其他分支只读\nif [ \"${GITHUB_REF_NAME}\" = \"develop\" ] || [ \"${GITHUB_REF_NAME}\" = \"main\" ]; then\n echo \"CACHE_MODE=read-write\" >> $GITHUB_ENV\n echo \"Cache mode: read-write (will push cache)\"\nelse\n echo \"CACHE_MODE=read-only\" >> $GITHUB_ENV\n echo \"Cache mode: read-only\"\nfi\n"
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push Worker image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/worker.Dockerfile \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"WORKER image pushed: ${IMAGE_TAG}\"
"
- name: Job duration summary
if: always()
shell: sh
run: "set +eu\nif [ -n \"$JOB_START_TIME\" ]; then\n END_TIME=$(date +%s)\n DURATION=$((END_TIME - JOB_START_TIME))\n MINS=$((DURATION / 60))\n SECS=$((DURATION % 60))\n echo \"JOB_DURATION_SECONDS=$DURATION\" >> $GITHUB_ENV\n echo \"=== Job Duration: ${MINS}m${SECS}s ===\"\nelse\n echo \"JOB_DURATION_SECONDS=0\" >> $GITHUB_ENV\n echo \"=== Job Duration: unknown ===\"\nfi\n"
- name: Notify on failure
continue-on-error: true
if: failure()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Staging Worker Image" python3 scripts/ci_notify.py
'
build-staging-web:
name: Build Staging Web Image
runs-on:
- saas
- build-farm
timeout-minutes: 30
timeout-minutes: ${{ matrix.timeout }}
needs:
- validate
- frontend-lint
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
strategy:
fail-fast: false
matrix:
include:
- service: api
service_display: API
dockerfile: infra/docker/api.Dockerfile
image_name: xiaoxia-saas-api
cache_name: api-cache
timeout: 30
- service: worker
service_display: Worker
dockerfile: infra/docker/worker.Dockerfile
image_name: xiaoxia-saas-worker
cache_name: worker-cache
timeout: 40
- service: web
service_display: Web
dockerfile: infra/docker/web.Dockerfile
image_name: xiaoxia-saas-web
cache_name: web-cache
timeout: 30
steps:
- name: Checkout code
shell: sh
@@ -592,26 +466,27 @@ echo \"WORKER image pushed: ${IMAGE_TAG}\"
shell: sh
run: "set -eu\n# develop/main 分支写回缓存,其他分支只读\nif [ \"${GITHUB_REF_NAME}\" = \"develop\" ] || [ \"${GITHUB_REF_NAME}\" = \"main\" ]; then\n echo \"CACHE_MODE=read-write\" >> $GITHUB_ENV\n echo \"Cache mode: read-write (will push cache)\"\nelse\n echo \"CACHE_MODE=read-only\" >> $GITHUB_ENV\n echo \"Cache mode: read-only\"\nfi\n"
- name: Build frontend assets (npm build)
if: matrix.service == 'web'
shell: sh
run: "set -eu\nNPM_CACHE_VOLUME=\"xiaoxia-npm-cache\"\nif ! docker volume inspect \"$NPM_CACHE_VOLUME\" >/dev/null 2>&1; then\n docker volume create \"$NPM_CACHE_VOLUME\" >/dev/null\n echo \"Created npm cache volume: $NPM_CACHE_VOLUME\"\nfi\n\ndocker run --rm -v \"$PWD:/workspace\" -v \"$NPM_CACHE_VOLUME:/workspace/apps/web/node_modules\" -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc \"npm ci && npx tsc --incremental --tsBuildInfoFile node_modules/.tsbuildinfo && npx vite build\"\n\ntest -f apps/web/dist/index.html\necho \"Frontend build complete: $(ls apps/web/dist/ | head -5)\"\n"
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push Web image (buildx cache)
- name: Build and push ${{ matrix.service_display }} image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\"
IMAGE_TAG=\"${REGISTRY}/${{ matrix.image_name }}:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/${{ matrix.cache_name }}:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/web.Dockerfile \
${{ matrix.dockerfile }} \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"WEB image pushed: ${IMAGE_TAG}\"
echo \"${{ matrix.service_display }} image pushed: ${IMAGE_TAG}\"
"
- name: Job duration summary
if: always()
@@ -625,7 +500,7 @@ echo \"WEB image pushed: ${IMAGE_TAG}\"
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Staging Web Image" python3 scripts/ci_notify.py
NOTIFY_MODE=failure JOB_NAME="Build Staging ${{ matrix.service_display }} Image" python3 scripts/ci_notify.py
'
deploy-staging:
@@ -633,9 +508,7 @@ echo \"WEB image pushed: ${IMAGE_TAG}\"
runs-on: saas
timeout-minutes: 15
needs:
- build-staging-api
- build-staging-worker
- build-staging-web
- build-staging
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
steps:
- name: Checkout code
@@ -819,158 +692,36 @@ echo \"WEB image pushed: ${IMAGE_TAG}\"
NOTIFY_MODE=failure JOB_NAME="Staging API Integration Tests" python3 scripts/ci_notify.py
'
build-production-api:
name: Build Production API Image
build-production:
name: Build Production ${{ matrix.service_display }} Image
runs-on:
- saas
- build-farm
timeout-minutes: 30
timeout-minutes: ${{ matrix.timeout }}
needs:
- validate
- frontend-lint
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: "set -eu\npython3 - <<'INNERPY'\nimport io, os, tarfile, time, urllib.request, urllib.error\nurl = f\"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz\"\nrequest = urllib.request.Request(url, headers={\"Authorization\": f\"token {os.environ['GITHUB_TOKEN']}\"})\nlast_err = None\nfor attempt in range(5):\n try:\n with urllib.request.urlopen(request, timeout=120) as response:\n archive = response.read()\n break\n except urllib.error.HTTPError as e:\n last_err = e\n if e.code >= 500 and attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...\")\n time.sleep(wait)\n continue\n raise\n except Exception as e:\n last_err = e\n if attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...\"\
)\n time.sleep(wait)\n continue\nelse:\n raise last_err\nwith tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:\n root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'\n for member in tar.getmembers():\n name = member.name\n if name == root_prefix[:-1]:\n continue\n if name.startswith(root_prefix):\n member.name = name[len(root_prefix):]\n if member.name:\n tar.extract(member, '.')\nINNERPY\n"
- name: Record job start time
shell: sh
run: 'set -eu
echo "JOB_START_TIME=$(date +%s)" >> $GITHUB_ENV
echo "Job started at $(date)"
'
- name: Docker login to Registry
shell: sh
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: 'set -eu
printf ''%s'' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin
echo "Docker login successful"
'
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push API image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-api:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/api-cache:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/api.Dockerfile \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"API image pushed: ${IMAGE_TAG}\"
"
- name: Job duration summary
if: always()
shell: sh
run: "set +eu\nif [ -n \"$JOB_START_TIME\" ]; then\n END_TIME=$(date +%s)\n DURATION=$((END_TIME - JOB_START_TIME))\n MINS=$((DURATION / 60))\n SECS=$((DURATION % 60))\n echo \"JOB_DURATION_SECONDS=$DURATION\" >> $GITHUB_ENV\n echo \"=== Job Duration: ${MINS}m${SECS}s ===\"\nelse\n echo \"JOB_DURATION_SECONDS=0\" >> $GITHUB_ENV\n echo \"=== Job Duration: unknown ===\"\nfi\n"
- name: Notify on failure
continue-on-error: true
if: failure()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Production API Image" python3 scripts/ci_notify.py
'
build-production-worker:
name: Build Production Worker Image
runs-on:
- saas
- build-farm
timeout-minutes: 40
needs:
- validate
- frontend-lint
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: "set -eu\npython3 - <<'INNERPY'\nimport io, os, tarfile, time, urllib.request, urllib.error\nurl = f\"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz\"\nrequest = urllib.request.Request(url, headers={\"Authorization\": f\"token {os.environ['GITHUB_TOKEN']}\"})\nlast_err = None\nfor attempt in range(5):\n try:\n with urllib.request.urlopen(request, timeout=120) as response:\n archive = response.read()\n break\n except urllib.error.HTTPError as e:\n last_err = e\n if e.code >= 500 and attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...\")\n time.sleep(wait)\n continue\n raise\n except Exception as e:\n last_err = e\n if attempt < 4:\n wait = 2 ** attempt\n print(f\"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...\"\
)\n time.sleep(wait)\n continue\nelse:\n raise last_err\nwith tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:\n root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'\n for member in tar.getmembers():\n name = member.name\n if name == root_prefix[:-1]:\n continue\n if name.startswith(root_prefix):\n member.name = name[len(root_prefix):]\n if member.name:\n tar.extract(member, '.')\nINNERPY\n"
- name: Record job start time
shell: sh
run: 'set -eu
echo "JOB_START_TIME=$(date +%s)" >> $GITHUB_ENV
echo "Job started at $(date)"
'
- name: Docker login to Registry
shell: sh
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: 'set -eu
printf ''%s'' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin
echo "Docker login successful"
'
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push Worker image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-worker:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/worker-cache:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/worker.Dockerfile \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"WORKER image pushed: ${IMAGE_TAG}\"
"
- name: Job duration summary
if: always()
shell: sh
run: "set +eu\nif [ -n \"$JOB_START_TIME\" ]; then\n END_TIME=$(date +%s)\n DURATION=$((END_TIME - JOB_START_TIME))\n MINS=$((DURATION / 60))\n SECS=$((DURATION % 60))\n echo \"JOB_DURATION_SECONDS=$DURATION\" >> $GITHUB_ENV\n echo \"=== Job Duration: ${MINS}m${SECS}s ===\"\nelse\n echo \"JOB_DURATION_SECONDS=0\" >> $GITHUB_ENV\n echo \"=== Job Duration: unknown ===\"\nfi\n"
- name: Notify on failure
continue-on-error: true
if: failure()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Production Worker Image" python3 scripts/ci_notify.py
'
build-production-web:
name: Build Production Web Image
runs-on:
- saas
- build-farm
timeout-minutes: 30
needs:
- validate
- frontend-lint
if: startsWith(github.ref, 'refs/tags/v')
strategy:
fail-fast: false
matrix:
include:
- service: api
service_display: API
dockerfile: infra/docker/api.Dockerfile
image_name: xiaoxia-saas-api
cache_name: api-cache
timeout: 30
- service: worker
service_display: Worker
dockerfile: infra/docker/worker.Dockerfile
image_name: xiaoxia-saas-worker
cache_name: worker-cache
timeout: 40
- service: web
service_display: Web
dockerfile: infra/docker/web.Dockerfile
image_name: xiaoxia-saas-web
cache_name: web-cache
timeout: 30
steps:
- name: Checkout code
shell: sh
@@ -999,29 +750,30 @@ echo \"WORKER image pushed: ${IMAGE_TAG}\"
'
- name: Build frontend assets (npm build)
if: matrix.service == 'web'
shell: sh
run: "set -eu\nNPM_CACHE_VOLUME=\"xiaoxia-npm-cache\"\nif ! docker volume inspect \"$NPM_CACHE_VOLUME\" >/dev/null 2>&1; then\n docker volume create \"$NPM_CACHE_VOLUME\" >/dev/null\nfi\n\ndocker run --rm -v \"$PWD:/workspace\" -v \"$NPM_CACHE_VOLUME:/workspace/apps/web/node_modules\" -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc \"npm ci && npx tsc --incremental --tsBuildInfoFile node_modules/.tsbuildinfo && npx vite build\"\n\ntest -f apps/web/dist/index.html\necho \"Frontend build complete\"\n"
- name: Setup buildx builder (docker-container driver)
shell: sh
run: "set -eu\n# 确保使用 docker-container driver 以支持 cache export 功能\nif ! docker buildx inspect ci-builder > /dev/null 2>&1; then\n docker buildx create --use --name ci-builder --driver docker-container\n echo \"Created ci-builder (docker-container driver)\"\nelse\n docker buildx use ci-builder\n echo \"Using existing ci-builder\"\nfi\ndocker buildx inspect --bootstrap\n"
- name: Build and push Web image (buildx cache)
- name: Build and push ${{ matrix.service_display }} image (buildx cache)
shell: sh
run: "set -eu
REGISTRY=\"git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas\"
IMAGE_TAG=\"${REGISTRY}/xiaoxia-saas-web:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/web-cache:${GITHUB_REF_NAME}\"
IMAGE_TAG=\"${REGISTRY}/${{ matrix.image_name }}:${GITHUB_SHA}\"
CACHE_REF=\"${REGISTRY}/${{ matrix.cache_name }}:${GITHUB_REF_NAME}\"
bash scripts/ci/docker_build_push.sh \
infra/docker/web.Dockerfile \
${{ matrix.dockerfile }} \
\"${IMAGE_TAG}\" \
\"${CACHE_REF}\" \
APP_VERSION=\"${GITHUB_SHA}\"
echo
echo \"WEB image pushed: ${IMAGE_TAG}\"
echo \"${{ matrix.service_display }} image pushed: ${IMAGE_TAG}\"
"
- name: Cleanup old Docker images
if: always()
if: always() && matrix.service == 'web'
shell: sh
run: "set -eu\nif [ -f scripts/cleanup_old_images.sh ]; then\n chmod +x scripts/cleanup_old_images.sh\n scripts/cleanup_old_images.sh\nelse\n echo \"Cleanup script not found, doing basic prune...\"\n docker image prune -f 2>/dev/null || true\nfi\necho \"Disk usage after cleanup:\"\ndf -h / | tail -1\n"
- name: Job duration summary
@@ -1036,7 +788,7 @@ echo \"WEB image pushed: ${IMAGE_TAG}\"
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: 'set +e
NOTIFY_MODE=failure JOB_NAME="Build Production Web Image" python3 scripts/ci_notify.py
NOTIFY_MODE=failure JOB_NAME="Build Production ${{ matrix.service_display }} Image" python3 scripts/ci_notify.py
'
deploy-production:
@@ -1045,9 +797,7 @@ echo \"WEB image pushed: ${IMAGE_TAG}\"
timeout-minutes: 30
if: startsWith(github.ref, 'refs/tags/v')
needs:
- build-production-api
- build-production-worker
- build-production-web
- build-production
steps:
- name: Checkout code
shell: sh