@@ -458,8 +458,6 @@ jobs:
name : Frontend Lint
runs-on : ci-l2
timeout-minutes : 10
needs : check-frontend-only
if : always() && needs.check-frontend-only.outputs.skip_frontend != 'true'
steps :
- name : Checkout code
shell : sh
@@ -574,12 +572,11 @@ jobs:
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
build-pr-backend :
name : PR Build ${{ matrix.service_display }} Image (Backend)
build-pr :
name : PR Build ${{ matrix.service_display }} Image
runs-on : runtime-builder
timeout-minutes : ${{ matrix.timeout }}
needs : check-frontend-only
if : always() && github.event_name == 'pull_request' && needs.check-frontend-only.outputs.skip_backend != 'true'
if : github.event_name == 'pull_request'
strategy :
fail-fast : false
matrix :
@@ -596,174 +593,6 @@ jobs:
image_name : xiaoxia-saas-worker
cache_name : worker-cache
timeout : 40
steps :
- name : Checkout code
shell : sh
env :
GITHUB_TOKEN : ${{ github.token }}
run : |
curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash
- name : Record job start time
shell : sh
run : bash scripts/ci/step_timer_start.sh
- name : Docker login to Registry (for cache read)
shell : sh
env :
ACR_USERNAME : ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD : ${{ secrets.ACR_PASSWORD }}
GITEA_REGISTRY_USER : xiaoxia
GITEA_REGISTRY_TOKEN : ${{ secrets.REGISTRY_TOKEN }}
run : |
set -eu
for i in 1 2 3; do
echo "Docker login attempt $i/3"
if printf '%s' "${ACR_PASSWORD}" | docker login xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com -u "${ACR_USERNAME}" --password-stdin && docker login git.xiaoxiajianji.com -u "${GITEA_REGISTRY_USER}" -p "${GITEA_REGISTRY_TOKEN}"; then
echo "Docker login successful"
break
fi
echo "Docker login failed ($i/3), retrying in 5s..."
sleep 5
done
- name : Pre-build worker base images (fallback if not exist)
if : matrix.service == 'worker'
id : prebuild
shell : sh
run : |
set -eu
REGISTRY="git.xiaoxiajianji.com/xiaoxia-saas"
BASE_BUILDER="${REGISTRY}/worker-base-builder:latest"
BASE_RUNTIME="${REGISTRY}/worker-base-runtime:latest"
# 尝试拉取基础镜像
echo "检查基础镜像..."
if docker pull "$BASE_BUILDER" 2>/dev/null && docker pull "$BASE_RUNTIME" 2>/dev/null; then
echo "基础镜像已存在,使用远程镜像"
echo "fallback=false" >> $GITHUB_OUTPUT
else
echo "基础镜像不存在,本地构建(fallback模式)..."
# 尝试用buildx构建,失败则回退到普通docker build( DooD模式下buildx builder偶发崩溃)
BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}"
BUILDX_AVAILABLE=true
if ! docker buildx create --use --name "$BUILDER_NAME" --driver docker-container > /dev/null 2>&1; then
BUILDX_AVAILABLE=false
fi
if [ "$BUILDX_AVAILABLE" = true ] && ! docker buildx inspect --bootstrap > /dev/null 2>&1; then
BUILDX_AVAILABLE=false
docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true
fi
build_base() {
local df="$1"
local tag="$2"
local name="$3"
if [ "$BUILDX_AVAILABLE" = true ]; then
echo "构建 $name( buildx) ..."
if docker buildx build --load -f "$df" -t "$tag" . > /dev/null 2>&1; then
echo "$name 构建成功"
return 0
fi
echo "buildx失败,回退到普通docker build"
BUILDX_AVAILABLE=false
docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true
fi
echo "构建 $name( docker build) ..."
docker build -f "$df" -t "$tag" .
}
build_base infra/docker/worker-base-builder.Dockerfile "$BASE_BUILDER" "worker-base-builder"
build_base infra/docker/worker-base-runtime.Dockerfile "$BASE_RUNTIME" "worker-base-runtime"
echo "fallback=true" >> $GITHUB_OUTPUT
echo "基础镜像本地构建完成"
fi
- name : Build PR image (verify only, no push)
shell : sh
run : |
set -eu
REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji"
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:pr-${GITHUB_SHA}"
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop"
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\""
if [ "${{ matrix.service }}" = "web" ]; then
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf"
fi
# Worker fallback模式:基础镜像本地已构建,用普通docker build绕过buildx
if [ "${{ matrix.service }}" = "worker" ] && [ "${{ steps.prebuild.outputs.fallback }}" = "true" ]; then
echo "Fallback模式:用普通docker build(基础镜像本地已构建)"
BUILD_ARG_STR=""
for arg in $EXTRA_BUILD_ARGS; do
BUILD_ARG_STR="$BUILD_ARG_STR --build-arg $arg"
done
docker build -f ${{ matrix.dockerfile }} -t "${IMAGE_TAG}" $BUILD_ARG_STR .
echo "Fallback PR Build successful"
exit 0
fi
NO_CACHE_FLAG=""
for i in 1 2 3; do
echo "PR Build attempt $i/3"
if bash scripts/ci/docker_build_only.sh $NO_CACHE_FLAG ${{ matrix.dockerfile }} "${IMAGE_TAG}" "${CACHE_REF}" $EXTRA_BUILD_ARGS; then
echo "PR Build successful"
break
fi
echo "PR Build failed (attempt $i/3)"
[ $i -eq 3 ] && exit 1
sleep 10
if [ $i -eq 2 ]; then
NO_CACHE_FLAG="--no-cache"
echo "Next retry with --no-cache"
fi
done
echo
echo "${{ matrix.service_display }} PR build verified: ${IMAGE_TAG}"
- name : Cleanup buildx builder
if : always()
shell : sh
run : |
BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}"
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
docker buildx prune -f 2>/dev/null || true
echo "Builder cleanup done"
- name : Job duration summary
if : always()
shell : sh
run : bash scripts/ci/step_timer_end.sh
- 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="PR Build ${{ matrix.service_display }} Image" python3 scripts/ci_notify.py
- name : Report CI trace
if : always()
shell : sh
env :
AGENTLOOP_LICENSE_KEY : ${{ secrets.AGENTLOOP_LICENSE_KEY }}
run : |
STATUS="ok"
[ ${{ job.status }} = "success" ] || STATUS="error"
START_TIME=""
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
build-pr-web :
name : PR Build Web Image
runs-on : runtime-builder
timeout-minutes : ${{ matrix.timeout }}
needs : check-frontend-only
if : always() && github.event_name == 'pull_request' && needs.check-frontend-only.outputs.skip_frontend != 'true'
strategy :
fail-fast : false
matrix :
include :
- service : web
service_display : Web
dockerfile : infra/docker/web.Dockerfile
@@ -927,7 +756,6 @@ jobs:
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
build-staging :
name : Build Staging ${{ matrix.service_display }} Image
runs-on : runtime-builder
@@ -1856,8 +1684,7 @@ jobs:
- integration-tests
- frontend-lint
- frontend-unit-test
- build-pr-backend
- build-pr-web
- build-pr
timeout-minutes : 3
steps :
- name : Checkout code
@@ -1884,8 +1711,7 @@ jobs:
RESULT_INTEGRATION : ${{ needs.integration-tests.result }}
RESULT_FRONTEND_LINT : ${{ needs.frontend-lint.result }}
RESULT_FRONTEND_UNIT : ${{ needs.frontend-unit-test.result }}
RESULT_BUILD_PR_BACKEND : ${{ needs.build-pr-backend .result }}
RESULT_BUILD_PR_WEB : ${{ needs.build-pr-web.result }}
RESULT_BUILD_PR : ${{ needs.build-pr.result }}
run : |
set -eu
echo "=== CI Gate 评估 ==="
@@ -1899,8 +1725,7 @@ jobs:
echo " integration-tests: $RESULT_INTEGRATION"
echo " frontend-lint: $RESULT_FRONTEND_LINT"
echo " frontend-unit-test: $RESULT_FRONTEND_UNIT"
echo " build-pr-backend : $RESULT_BUILD_PR_BACKEND "
echo " build-pr-web: $RESULT_BUILD_PR_WEB"
echo " build-pr: $RESULT_BUILD_PR"
echo ""
# 判断PR类型
@@ -1914,19 +1739,18 @@ jobs:
"validate-code-quality:$RESULT_CODE_QUALITY"
"validate-type-check:$RESULT_TYPE_CHECK"
"validate-migration:$RESULT_MIGRATION"
"frontend-lint:$RESULT_FRONTEND_LINT"
"build-pr:$RESULT_BUILD_PR"
)
# 后端检查
REQUIRED_BACKEND=(
"unit-tests:$RESULT_UNIT_TESTS"
"build-pr-backend:$RESULT_BUILD_PR_BACKEND"
)
# 前端检查
REQUIRED_FRONTEND=(
"frontend-lint:$RESULT_FRONTEND_LINT"
"frontend-unit-test:$RESULT_FRONTEND_UNIT"
"build-pr-web:$RESULT_BUILD_PR_WEB"
)
ALL_PASSED=true