From f678504b972594b972ad26ac5996086d594108c0 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:44:05 +0800 Subject: [PATCH 01/19] =?UTF-8?q?ci(P0-1):=20=E7=A7=BB=E9=99=A4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=B5=8B=E8=AF=95=E8=B4=A6=E5=8F=B7=E5=AF=86=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E7=94=A8=20Gitea=20Secrets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 STAGING_TEST_USER / STAGING_TEST_PASSWORD 两个 secret - 修改 daily-check.yml 中 5 处明文凭据引用 - 涉及 staging-api-tests 和 performance-check 两个 job --- .gitea/workflows/daily-check.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/daily-check.yml b/.gitea/workflows/daily-check.yml index 209ae1283..4c03e38d6 100644 --- a/.gitea/workflows/daily-check.yml +++ b/.gitea/workflows/daily-check.yml @@ -171,8 +171,8 @@ jobs: docker run --rm \ -e BASE_URL=https://staging-api.xiaoxiajianji.com \ -e WEB_URL=https://staging.xiaoxiajianji.com \ - -e TEST_USER=18314979086@163.com \ - -e TEST_PASSWORD=Ying1234 \ + -e TEST_USER=${{ secrets.STAGING_TEST_USER }} \ + -e TEST_PASSWORD=${{ secrets.STAGING_TEST_PASSWORD }} \ -e CLEANUP_ENABLED=1 \ -e PERF_CHECK_ENABLED=1 \ -e PERF_WARN_THRESHOLD_MS=500 \ @@ -344,6 +344,9 @@ jobs: - name: Run performance baseline checks id: perf shell: sh + env: + STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }} + STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }} run: | set +e START_TIME=$(date +%s) @@ -381,7 +384,7 @@ jobs: echo "--- 准备: 获取测试 Token ---" AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ -H "Content-Type: application/json" \ - -d '{"email":"18314979086@163.com","password":"Ying1234"}' \ + -d "{"email":"${STAGING_TEST_USER}","password":"${STAGING_TEST_PASSWORD}"}" \ "https://staging-api.xiaoxiajianji.com/api/v1/auth/login" \ --max-time 10 2>&1) AUTH_CODE=$(echo "$AUTH_RESP" | tail -1) @@ -411,7 +414,7 @@ jobs: # 构建 curl 命令 CURL_ARGS="-s -o /dev/null -w '%{http_code} %{time_total}' --max-time 30" if [ "$method" = "POST" ]; then - CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"18314979086@163.com\",\"password\":\"Ying1234\"}'" + CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"${STAGING_TEST_USER}\",\"password\":\"${STAGING_TEST_PASSWORD}\"}'" fi if [ -n "$TOKEN" ] && [ "$name" != "健康检查" ]; then CURL_ARGS="$CURL_ARGS -H 'Authorization: Bearer $TOKEN'" @@ -459,6 +462,9 @@ jobs: - name: Generate performance report id: report shell: sh + env: + STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }} + STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }} run: | set +e echo "" @@ -476,7 +482,7 @@ jobs: # 先登录获取 token AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ -H "Content-Type: application/json" \ - -d '{"email":"18314979086@163.com","password":"Ying1234"}' \ + -d "{"email":"${STAGING_TEST_USER}","password":"${STAGING_TEST_PASSWORD}"}" \ "https://staging-api.xiaoxiajianji.com/api/v1/auth/login" \ --max-time 10 2>&1) AUTH_CODE=$(echo "$AUTH_RESP" | tail -1) @@ -492,7 +498,7 @@ jobs: local CURL_ARGS="-s -o /dev/null -w '%{http_code} %{time_total}' --max-time 30" if [ "$method" = "POST" ]; then - CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"18314979086@163.com\",\"password\":\"Ying1234\"}'" + CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"${STAGING_TEST_USER}\",\"password\":\"${STAGING_TEST_PASSWORD}\"}'" fi if [ -n "$TOKEN" ] && [ "$name" != "健康检查" ]; then CURL_ARGS="$CURL_ARGS -H 'Authorization: Bearer $TOKEN'" -- 2.54.0 From b012a7da0010565cc3d160e576e80ff6e2bd3d23 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:44:17 +0800 Subject: [PATCH 02/19] =?UTF-8?q?ci(P0-2):=20=E5=88=A0=E9=99=A4=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E6=AE=8B=E7=95=99=E7=9A=84=20test-ssh-secret.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该文件是用于调试 SSH secret 配置的临时 workflow, 仅在修改自身时触发,无实际业务用途,应从生产 workflow 目录移除。 --- .gitea/workflows/test-ssh-secret.yml | 69 ---------------------------- 1 file changed, 69 deletions(-) delete mode 100644 .gitea/workflows/test-ssh-secret.yml diff --git a/.gitea/workflows/test-ssh-secret.yml b/.gitea/workflows/test-ssh-secret.yml deleted file mode 100644 index c457a7aec..000000000 --- a/.gitea/workflows/test-ssh-secret.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Test SSH Secret -on: - push: - branches: [develop] - paths: - - '.gitea/workflows/test-ssh-secret.yml' - -jobs: - test-ssh: - runs-on: ubuntu-22.04 - steps: - - name: Install SSH client - run: | - which ssh || (apt-get update && apt-get install -y openssh-client) - ssh -V - - - name: Debug environment - run: | - echo "=== Environment ===" - echo "Runner hostname: $(hostname)" - echo "Runner IP: $(hostname -i || echo 'unknown')" - echo "Current user: $(whoami)" - echo "=== Secrets check ===" - if [ -n "$STAGING_SSH_HOST" ]; then - echo "STAGING_SSH_HOST: [SET] value_length=${#STAGING_SSH_HOST}" - else - echo "STAGING_SSH_HOST: [EMPTY]" - fi - if [ -n "$STAGING_SSH_USER" ]; then - echo "STAGING_SSH_USER: [SET] value_length=${#STAGING_SSH_USER}" - else - echo "STAGING_SSH_USER: [EMPTY]" - fi - if [ -n "$STAGING_SSH_KEY" ]; then - echo "STAGING_SSH_KEY: [SET] value_length=${#STAGING_SSH_KEY}" - else - echo "STAGING_SSH_KEY: [EMPTY]" - fi - env: - STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }} - STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }} - STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }} - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - chmod 700 ~/.ssh - echo "$STAGING_SSH_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub 2>/dev/null || echo "No public key generated" - echo "=== SSH Key fingerprint ===" - ssh-keygen -lf ~/.ssh/id_ed25519 || echo "Key fingerprint failed" - env: - STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }} - - - name: Test SSH connection - run: | - echo "Attempting SSH connection to $STAGING_SSH_HOST..." - ssh -i ~/.ssh/id_ed25519 \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - -o ConnectTimeout=10 \ - -o BatchMode=yes \ - -v \ - $STAGING_SSH_USER@$STAGING_SSH_HOST "echo 'SSH_CONNECTION_SUCCESS' && hostname && whoami" - echo "=== SSH Test Complete ===" - env: - STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }} - STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }} -- 2.54.0 From bc83a361b10070bf4420b204497fa381bc79113a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:45:40 +0800 Subject: [PATCH 03/19] =?UTF-8?q?ci(P0-3):=20=E4=BF=AE=E5=A4=8D=20daily-ch?= =?UTF-8?q?eck.yml=20=E4=B8=AD=20job=20outputs=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - staging-api-tests.outputs.report: steps.smoke → steps.report (smoke step 输出的是 api_report,report step 才输出 report) - staging-e2e.outputs.report: steps.smoke → steps.e2e (该 job 的 step id 是 e2e,不是 smoke) - 修复后 daily-report job 才能正确获取各巡检项状态 --- .gitea/workflows/daily-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/daily-check.yml b/.gitea/workflows/daily-check.yml index 4c03e38d6..766e5577d 100644 --- a/.gitea/workflows/daily-check.yml +++ b/.gitea/workflows/daily-check.yml @@ -112,7 +112,7 @@ jobs: runs-on: saas timeout-minutes: 10 outputs: - report: ${{ steps.smoke.outputs.report }} + report: ${{ steps.report.outputs.report }} steps: - name: Checkout code @@ -249,7 +249,7 @@ jobs: runs-on: saas timeout-minutes: 15 outputs: - report: ${{ steps.smoke.outputs.report }} + report: ${{ steps.e2e.outputs.report }} steps: - name: Checkout code -- 2.54.0 From ed398b8c78bde0841e01028c8e23aa347bb74bb1 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:46:11 +0800 Subject: [PATCH 04/19] =?UTF-8?q?ci(P1-1):=20=E5=88=A0=E9=99=A4=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E7=9A=84=20tests.yml=20=E6=B5=81=E6=B0=B4=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ci-cd.yml 的 validate job 已经覆盖了 tests.yml 的全部功能: - 单元测试 + 集成测试 - Black 代码格式化检查 - Flake8 静态分析 - 还有安全扫描、迁移检查等 tests.yml 没有的检查 且 ci-cd.yml 已配置 pull_request 触发,PR 时会自动运行。 删除重复流水线,减少维护成本。 --- .gitea/workflows/tests.yml | 163 ------------------------------------- 1 file changed, 163 deletions(-) delete mode 100755 .gitea/workflows/tests.yml diff --git a/.gitea/workflows/tests.yml b/.gitea/workflows/tests.yml deleted file mode 100755 index f8ffeb555..000000000 --- a/.gitea/workflows/tests.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: Tests - -on: - pull_request: - branches: [ main ] - -jobs: - test: - runs-on: runtime-builder - - steps: - - name: Checkout code - shell: sh - run: | - set -eu - python - <<'PY' - import io - import os - import tarfile - import time - import urllib.error - import urllib.request - - url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" - request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) - # Retry up to 5 times with backoff for transient 5xx errors - last_err = None - for attempt in range(5): - try: - with urllib.request.urlopen(request, timeout=120) as response: - archive = response.read() - break - except urllib.error.HTTPError as e: - last_err = e - if e.code >= 500 and attempt < 4: - wait = 2 ** attempt - print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") - time.sleep(wait) - continue - raise - except Exception as e: - last_err = e - if attempt < 4: - wait = 2 ** attempt - print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") - time.sleep(wait) - continue - raise - else: - raise last_err - - with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: - root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' - for member in tar.getmembers(): - name = member.name - if name == root_prefix[:-1]: - continue - if name.startswith(root_prefix): - member.name = name[len(root_prefix):] - if member.name: - tar.extract(member, '.') - PY - - - name: Show Python version - shell: sh - run: | - set -eu - python --version - python -m pip --version - - - name: Install dependencies - shell: sh - run: | - set -eu - python -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - python -m pip install -r requirements.txt -r requirements-dev.txt -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - - - name: Run unit tests - shell: sh - run: | - set -eu - PYTHONPATH="$PWD/apps/api:$PWD" python -m pytest tests/unit -q - - - name: Run integration tests - shell: sh - run: | - set -eu - PYTHONPATH="$PWD/apps/api:$PWD" python -m pytest tests/integration -q --timeout=60 -x - - lint: - runs-on: runtime-builder - - steps: - - name: Checkout code - shell: sh - run: | - set -eu - python - <<'PY' - import io - import os - import tarfile - import time - import urllib.error - import urllib.request - - url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" - request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) - # Retry up to 5 times with backoff for transient 5xx errors - last_err = None - for attempt in range(5): - try: - with urllib.request.urlopen(request, timeout=120) as response: - archive = response.read() - break - except urllib.error.HTTPError as e: - last_err = e - if e.code >= 500 and attempt < 4: - wait = 2 ** attempt - print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") - time.sleep(wait) - continue - raise - except Exception as e: - last_err = e - if attempt < 4: - wait = 2 ** attempt - print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") - time.sleep(wait) - continue - raise - else: - raise last_err - - with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: - root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' - for member in tar.getmembers(): - name = member.name - if name == root_prefix[:-1]: - continue - if name.startswith(root_prefix): - member.name = name[len(root_prefix):] - if member.name: - tar.extract(member, '.') - PY - - - name: Install dependencies - shell: sh - run: | - set -eu - python -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - python -m pip install -r requirements.txt -r requirements-dev.txt -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - - - name: Run Black (check only) - shell: sh - run: | - set -eu - python -m black --check alembic apps packages tests scripts - - - name: Run Flake8 - shell: sh - run: | - set -eu - python -m flake8 apps packages tests --count --statistics -- 2.54.0 From 5772de2ade35d950bb288923e5b7d60bc5139b34 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:47:07 +0800 Subject: [PATCH 05/19] =?UTF-8?q?ci(P1-2):=20=E5=88=A0=E9=99=A4=20performa?= =?UTF-8?q?nce-check=20=E4=B8=AD=E7=AC=AC=E4=B8=80=E4=B8=AA=E6=AD=BB?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit performance-check job 中有两个 step 都在做性能测试: - 第一个 step(118行)使用 while read 子 shell 方式,变量全部丢失,结果完全没用 - 第二个 step 使用函数方式,正确统计结果 删除无用的第一个 step,减少约一半的请求次数和执行时间。 --- .gitea/workflows/daily-check.yml | 118 ------------------------------- 1 file changed, 118 deletions(-) diff --git a/.gitea/workflows/daily-check.yml b/.gitea/workflows/daily-check.yml index 766e5577d..2f147825f 100644 --- a/.gitea/workflows/daily-check.yml +++ b/.gitea/workflows/daily-check.yml @@ -341,124 +341,6 @@ jobs: report: ${{ steps.report.outputs.report }} steps: - - name: Run performance baseline checks - id: perf - shell: sh - env: - STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }} - STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }} - run: | - set +e - START_TIME=$(date +%s) - echo "==========================================" - echo " 性能基线巡检 - Staging API" - echo " 目标: https://staging-api.xiaoxiajianji.com" - echo "==========================================" - echo "" - - TOTAL=0 - PASS=0 - FAIL=0 - WARN=0 - WARN_LIST="" - FAIL_LIST="" - - # 核心接口配置: 名称|路径|方法|阈值(ms)|失败阈值(ms) - # 核心接口(core): 500ms - # 普通接口(normal): 1000ms - # 重操作接口(heavy): 3000ms - ENDPOINTS=" - 登录|/api/v1/auth/login|POST|500|3000 - 获取当前用户|/api/v1/auth/me|GET|500|3000 - 项目列表|/api/v1/projects|GET|500|3000 - 素材列表|/api/v1/assets|GET|500|3000 - 模板列表|/api/v1/templates|GET|500|3000 - 剪辑计划列表|/api/v1/edit-plans|GET|500|3000 - 生成任务列表|/api/v1/generation/tasks|GET|500|3000 - 订阅信息|/api/v1/subscription/current|GET|500|3000 - 音色列表|/api/v1/voices|GET|1000|5000 - 健康检查|/health|GET|200|1000 - " - - # 先登录获取 token - echo "--- 准备: 获取测试 Token ---" - AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ - -H "Content-Type: application/json" \ - -d "{"email":"${STAGING_TEST_USER}","password":"${STAGING_TEST_PASSWORD}"}" \ - "https://staging-api.xiaoxiajianji.com/api/v1/auth/login" \ - --max-time 10 2>&1) - AUTH_CODE=$(echo "$AUTH_RESP" | tail -1) - AUTH_BODY=$(echo "$AUTH_RESP" | sed '$d') - - if [ "$AUTH_CODE" = "200" ]; then - TOKEN=$(echo "$AUTH_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token',''))" 2>/dev/null) - if [ -n "$TOKEN" ]; then - echo "Token 获取成功" - else - echo "Token 解析失败,部分接口可能无法测试" - TOKEN="" - fi - else - echo "登录失败 (HTTP $AUTH_CODE),部分接口将跳过鉴权测试" - TOKEN="" - fi - - echo "" - echo "--- 开始性能测试 ---" - echo "" - - echo "$ENDPOINTS" | while IFS='|' read -r name path method warn_ms fail_ms; do - [ -z "$name" ] && continue - TOTAL=$((TOTAL + 1)) - - # 构建 curl 命令 - CURL_ARGS="-s -o /dev/null -w '%{http_code} %{time_total}' --max-time 30" - if [ "$method" = "POST" ]; then - CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"${STAGING_TEST_USER}\",\"password\":\"${STAGING_TEST_PASSWORD}\"}'" - fi - if [ -n "$TOKEN" ] && [ "$name" != "健康检查" ]; then - CURL_ARGS="$CURL_ARGS -H 'Authorization: Bearer $TOKEN'" - fi - - # 执行请求 - RESP=$(eval curl $CURL_ARGS "https://staging-api.xiaoxiajianji.com${path}" 2>&1) - HTTP_CODE=$(echo "$RESP" | awk '{print $1}') - TIME_TOTAL=$(echo "$RESP" | awk '{print $2}') - ELAPSED_MS=$(python3 -c "print(int(float('${TIME_TOTAL:-0}') * 1000))" 2>/dev/null || echo "0") - - if [ "$HTTP_CODE" -ge 500 ] 2>/dev/null; then - FAIL=$((FAIL + 1)) - FAIL_LIST="$FAIL_LIST\n ❌ $name - HTTP $HTTP_CODE (${ELAPSED_MS}ms)" - echo "❌ $name - HTTP $HTTP_CODE - ${ELAPSED_MS}ms (FAIL)" - elif [ "$ELAPSED_MS" -ge "$fail_ms" ] 2>/dev/null; then - FAIL=$((FAIL + 1)) - FAIL_LIST="$FAIL_LIST\n ❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms" - echo "❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms (FAIL)" - elif [ "$ELAPSED_MS" -ge "$warn_ms" ] 2>/dev/null; then - WARN=$((WARN + 1)) - WARN_LIST="$WARN_LIST\n ⚠️ $name - ${ELAPSED_MS}ms > ${warn_ms}ms" - echo "⚠️ $name - ${ELAPSED_MS}ms (WARN, threshold: ${warn_ms}ms)" - PASS=$((PASS + 1)) - else - PASS=$((PASS + 1)) - echo "✅ $name - ${ELAPSED_MS}ms (OK, threshold: ${warn_ms}ms)" - fi - done - - # 由于 while 在子 shell 中执行,用文件传递结果 - # 重新跑一次用文件计数方式 - echo "" - echo "--- 汇总性能数据 ---" - - END_TIME=$(date +%s) - ELAPSED=$((END_TIME - START_TIME)) - - echo "" - echo "========== 性能基线巡检报告 ==========" - echo "环境: https://staging-api.xiaoxiajianji.com" - echo "耗时: ${ELAPSED}s" - echo "======================================" - - name: Generate performance report id: report shell: sh -- 2.54.0 From 2a6d3f0d8b7a6e0a424be2b06f60d23bd4e94c2f Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:49:20 +0800 Subject: [PATCH 06/19] =?UTF-8?q?ci(P1-5):=20=E7=BB=9F=E4=B8=80=20build=5F?= =?UTF-8?q?release=5Fimages.sh=20=E7=9A=84=20:dev=20tag=20=E5=B9=B6?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E8=87=B3=20Registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/build_release_images.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/build_release_images.sh b/scripts/build_release_images.sh index 3b2ab1947..1a57aac18 100755 --- a/scripts/build_release_images.sh +++ b/scripts/build_release_images.sh @@ -35,6 +35,7 @@ CACHE_TAG="${CACHE_TAG:-release}" API_IMAGE="xiaoxia-saas-api:$VERSION" WORKER_IMAGE="xiaoxia-saas-worker:$VERSION" WEB_IMAGE="xiaoxia-saas-web:$VERSION" +WEB_LATEST="xiaoxia-saas-web:dev" API_LATEST="xiaoxia-saas-api:dev" WORKER_LATEST="xiaoxia-saas-worker:dev" @@ -96,14 +97,14 @@ if [ "$USE_CACHE" -eq 1 ]; then --cache-to "type=registry,ref=${CACHE_REGISTRY}/web-cache:${CACHE_TAG},mode=max" \ -f infra/docker/web-artifact.Dockerfile \ --build-arg "NGINX_CONF=$NGINX_CONF_FILE" \ - -t "$WEB_IMAGE" \ + -t "$WEB_IMAGE" -t "$WEB_LATEST" \ --load \ . else docker build --pull=false \ -f infra/docker/web-artifact.Dockerfile \ --build-arg "NGINX_CONF=$NGINX_CONF_FILE" \ - -t "$WEB_IMAGE" \ + -t "$WEB_IMAGE" -t "$WEB_LATEST" \ . fi @@ -116,7 +117,17 @@ if [ "$USE_PUSH" -eq 1 ]; then docker push "$REGISTRY_API" docker push "$REGISTRY_WORKER" docker push "$REGISTRY_WEB" - echo "All images pushed to $REGISTRY" + # 同时推送 :dev tag(用于快速拉取最新开发版) + REGISTRY_API_DEV="${REGISTRY}/xiaoxia-saas-api:dev" + REGISTRY_WORKER_DEV="${REGISTRY}/xiaoxia-saas-worker:dev" + REGISTRY_WEB_DEV="${REGISTRY}/xiaoxia-saas-web:dev" + docker tag "$API_LATEST" "$REGISTRY_API_DEV" + docker tag "$WORKER_LATEST" "$REGISTRY_WORKER_DEV" + docker tag "$WEB_LATEST" "$REGISTRY_WEB_DEV" + docker push "$REGISTRY_API_DEV" + docker push "$REGISTRY_WORKER_DEV" + docker push "$REGISTRY_WEB_DEV" + echo "All images + :dev tag pushed to $REGISTRY" else echo "Registry push skipped (no auth token available)" fi -- 2.54.0 From 90e5f4797509858afc4ac91addcc49afe78c3bf2 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:50:04 +0800 Subject: [PATCH 07/19] =?UTF-8?q?ci(P1-6):=20=E5=A2=9E=E5=BC=BA=20auto=5Fm?= =?UTF-8?q?erge=5Fprs.sh=20=E5=AE=89=E5=85=A8=E6=A3=80=E6=9F=A5=EF=BC=9ACI?= =?UTF-8?q?=E7=8A=B6=E6=80=81+=E6=A0=87=E7=AD=BE=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95+Review=E5=AE=A1=E6=89=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/auto_merge_prs.sh | 132 ++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 14 deletions(-) diff --git a/scripts/auto_merge_prs.sh b/scripts/auto_merge_prs.sh index 720b865a7..a710dfa18 100755 --- a/scripts/auto_merge_prs.sh +++ b/scripts/auto_merge_prs.sh @@ -1,43 +1,147 @@ #!/bin/bash -# 自动合并通过 CI 检查的 PR +# 自动合并通过 CI 检查且打了 auto-merge 标签的 PR # 用法: ./scripts/auto_merge_prs.sh [target_branch] +# 安全规则: +# 1. PR 必须打有 auto-merge 标签(白名单) +# 2. 所有 CI 检查必须通过 +# 3. 必须至少有 1 个 review approve(可通过 REQUIRE_APPROVAL=0 关闭) +# 4. mergeable 状态为 true + +set -eu GITEA_API="https://git.xiaoxiajianji.com/api/v1" TOKEN="${GITEA_API_TOKEN:?Please set GITEA_API_TOKEN environment variable}" REPO="xiaoxia/xiaoxia-saas" TARGET_BRANCH="${1:-develop}" +REQUIRE_APPROVAL="${REQUIRE_APPROVAL:-1}" +AUTO_MERGE_LABEL="${AUTO_MERGE_LABEL:-auto-merge}" -echo "=== Checking open PRs targeting $TARGET_BRANCH ===" +echo "=== Auto-merge check for PRs targeting $TARGET_BRANCH ===" +echo " Require approval: $REQUIRE_APPROVAL" +echo " Required label: $AUTO_MERGE_LABEL" +echo "" -# 获取所有 open PR +# 获取所有 open PR(包含标签信息) PRS=$(curl -s -H "Authorization: token $TOKEN" \ - "$GITEA_API/repos/$REPO/pulls?state=open&labels=0" | python3 -c " + "$GITEA_API/repos/$REPO/pulls?state=open" | python3 -c " import json, sys data = json.load(sys.stdin) for pr in data: - if pr.get('base', {}).get('ref') == '$TARGET_BRANCH': - if pr.get('mergeable', False): - print(f\"{pr['number']}|{pr['title']}|{pr.get('mergeable', 'unknown')}\") + if pr.get('base', {}).get('ref') != '$TARGET_BRANCH': + continue + number = pr['number'] + title = pr['title'] + mergeable = pr.get('mergeable', False) + labels = [l['name'] for l in pr.get('labels', [])] + head_sha = pr.get('head', {}).get('sha', '') + print(f'{number}|{title}|{mergeable}|{head_sha}|{\",\".join(labels)}') ") if [ -z "$PRS" ]; then - echo "No mergeable PRs found for $TARGET_BRANCH" + echo "No open PRs found for $TARGET_BRANCH" exit 0 fi -echo "$PRS" | while IFS='|' read -r number title mergeable; do - echo "Merging PR #$number: $title" +merged_count=0 +skipped_count=0 + +echo "$PRS" | while IFS='|' read -r number title mergeable head_sha labels; do + echo "--- PR #$number: $title ---" + echo " mergeable: $mergeable" + echo " labels: $labels" + echo " head_sha: ${head_sha:0:12}" + + # 检查 1: mergeable 状态 + if [ "$mergeable" != "True" ] && [ "$mergeable" != "true" ]; then + echo " ⏭️ Skip: not mergeable" + skipped_count=$((skipped_count + 1)) + continue + fi + + # 检查 2: auto-merge 标签白名单 + has_label=$(echo "$labels" | tr ',' '\n' | grep -qx "$AUTO_MERGE_LABEL" && echo "yes" || echo "no") + if [ "$has_label" != "yes" ]; then + echo " ⏭️ Skip: missing '$AUTO_MERGE_LABEL' label" + skipped_count=$((skipped_count + 1)) + continue + fi + + # 检查 3: CI 状态检查(所有 check 必须成功) + if [ -n "$head_sha" ]; then + ci_result=$(curl -s -H "Authorization: token $TOKEN" \ + "$GITEA_API/repos/$REPO/commits/$head_sha/status" | python3 -c " +import json, sys +data = json.load(sys.stdin) +status = data.get('state', 'unknown') +statuses = data.get('statuses', []) +# 统计各状态 +success = sum(1 for s in statuses if s.get('state') == 'success') +pending = sum(1 for s in statuses if s.get('state') == 'pending') +failure = sum(1 for s in statuses if s.get('state') in ('failure', 'error')) +total = len(statuses) +print(f'{status}|{total}|{success}|{pending}|{failure}') +") + ci_state=$(echo "$ci_result" | cut -d'|' -f1) + ci_total=$(echo "$ci_result" | cut -d'|' -f2) + ci_success=$(echo "$ci_result" | cut -d'|' -f3) + ci_pending=$(echo "$ci_result" | cut -d'|' -f4) + ci_failure=$(echo "$ci_result" | cut -d'|' -f5) + echo " CI status: $ci_state ($ci_success/$ci_total passed, $ci_pending pending, $ci_failure failed)" + + if [ "$ci_state" != "success" ]; then + echo " ⏭️ Skip: CI not passing (state=$ci_state)" + skipped_count=$((skipped_count + 1)) + continue + fi + else + echo " ⚠️ No head SHA found, skipping CI check" + fi + + # 检查 4: Review approve 检查 + if [ "$REQUIRE_APPROVAL" = "1" ]; then + review_result=$(curl -s -H "Authorization: token $TOKEN" \ + "$GITEA_API/repos/$REPO/pulls/$number/reviews" | python3 -c " +import json, sys +data = json.load(sys.stdin) +approved = sum(1 for r in data if r.get('state') == 'APPROVED') +changes_req = sum(1 for r in data if r.get('state') == 'CHANGES_REQUESTED') +print(f'{approved}|{changes_req}') +") + approved=$(echo "$review_result" | cut -d'|' -f1) + changes_req=$(echo "$review_result" | cut -d'|' -f2) + echo " Reviews: $approved approved, $changes_req changes requested" + + if [ "$approved" -lt 1 ]; then + echo " ⏭️ Skip: no approval yet" + skipped_count=$((skipped_count + 1)) + continue + fi + if [ "$changes_req" -gt 0 ]; then + echo " ⏭️ Skip: has changes requested" + skipped_count=$((skipped_count + 1)) + continue + fi + fi + + # 全部检查通过,执行合并 + echo " ✅ All checks passed, merging..." RESULT=$(curl -s -X POST \ -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ "$GITEA_API/repos/$REPO/pulls/$number/merge" \ - -d '{\"merge_method\": \"merge\"}') - + -d '{"merge_method": "squash"}') + if echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if 'id' in d else 1)"; then - echo " ✅ PR #$number merged successfully" + echo " ✅ PR #$number merged successfully (squash)" + merged_count=$((merged_count + 1)) else - echo " ❌ PR #$number failed: $RESULT" + error_msg=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('message', 'unknown error'))" 2>/dev/null || echo "$RESULT") + echo " ❌ PR #$number failed: $error_msg" fi + echo "" done +echo "" echo "=== Done ===" +echo " Merged: $merged_count" +echo " Skipped: $skipped_count" -- 2.54.0 From c6d1136ae380377bdaa66d13684ca9e2753b9b1c Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:50:49 +0800 Subject: [PATCH 08/19] =?UTF-8?q?ci(P1-7):=20=E9=87=8D=E5=86=99=20cleanup?= =?UTF-8?q?=5Fold=5Fimages.sh=EF=BC=9A=E7=A7=BB=E9=99=A4=E8=BF=87=E6=97=B6?= =?UTF-8?q?Registry=E6=B8=85=E7=90=86=E5=92=8C=E6=98=8E=E6=96=87=E5=AF=86?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E4=BB=85=E4=BF=9D=E7=95=99=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/cleanup_old_images.sh | 59 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/scripts/cleanup_old_images.sh b/scripts/cleanup_old_images.sh index 5860ae33b..0318bba61 100755 --- a/scripts/cleanup_old_images.sh +++ b/scripts/cleanup_old_images.sh @@ -1,49 +1,42 @@ #!/bin/sh # cleanup_old_images.sh -# 清理构建服务器上的旧 Docker 镜像和 Registry 旧版本 -# 保留最近 KEEP_VERSIONS 个版本(默认 2) +# 清理构建服务器上的旧 Docker 本地镜像 +# 保留最近 KEEP_VERSIONS 个版本(默认 5) # 在 CI 构建完成后调用,防止磁盘空间耗尽 +# +# 注意:Registry 侧的旧镜像清理已由 Gitea Package 清理规则接管, +# 本脚本仅负责构建服务器本地镜像清理。 + set -eu -KEEP_VERSIONS="${KEEP_VERSIONS:-2}" -REGISTRY_HOST="${REGISTRY_HOST:-172.30.18.198:5000}" +KEEP_VERSIONS="${KEEP_VERSIONS:-5}" SERVICES="xiaoxia-saas-api xiaoxia-saas-worker xiaoxia-saas-web" -ACCEPT="application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json" -echo "=== Docker Image Cleanup ===" +echo "=== Local Docker Image Cleanup ===" echo "Keeping last ${KEEP_VERSIONS} versions per service" echo "" for svc in $SERVICES; do - # 收集所有 v0.N.N 格式的版本号(去重,按版本号排序) - versions=$(docker images --format "{{.Repository}}:{{.Tag}}" | \ + # 收集所有版本标签(去重,按版本号排序) + versions=$(docker images --format "{{.Repository}}:{{.Tag}}" 2>/dev/null | \ grep "${svc}" | \ - grep -E "v[0-9]+\.[0-9]+\.[0-9]+" | \ - sed -E "s/.*:v([0-9]+\.[0-9]+\.[0-9]+).*/v\1/" | \ + grep -E ":(v[0-9]+\.[0-9]+\.[0-9]+|[a-f0-9]{7,})$" | \ + sed -E "s/.*:([^:]+)$/\1/" | \ sort -t. -k1,1V -k2,2n -k3,3n | \ uniq) - total=$(echo "$versions" | grep -c "^v" || true) + total=$(echo "$versions" | grep -c . || true) if [ "$total" -gt "$KEEP_VERSIONS" ]; then remove_count=$((total - KEEP_VERSIONS)) to_remove=$(echo "$versions" | head -n "$remove_count") - echo "[Registry] Cleaning old blobs for ${svc}..." + echo "[${svc}] ${total} versions found, removing ${remove_count} oldest..." for ver in $to_remove; do - # 仓库名与服务名一致(如 xiaoxia-saas-api),不再裁剪前缀 - manifest_url="http://admin:Xiaoxia2026@localhost:5000/v2/${svc}/manifests/${ver}" - digest=$(curl -s -D- -H "Accept: ${ACCEPT}" "$manifest_url" | grep -i "^docker-content-digest:" | tr -d "\r" | awk "{print \$2}") - if [ -n "$digest" ]; then - curl -s -X DELETE -H "Accept: ${ACCEPT}" "http://admin:Xiaoxia2026@localhost:5000/v2/${svc}/manifests/${digest}" > /dev/null 2>&1 || true - echo " Deleted registry tag: ${svc}:${ver}" - fi - done - - echo "[Local] Removing old local images for ${svc}..." - for ver in $to_remove; do - docker rmi "${svc}:${ver}" 2>/dev/null && echo " Removed local: ${svc}:${ver}" || true - docker rmi "${REGISTRY_HOST}/${svc}:${ver}" 2>/dev/null && echo " Removed registry-ref: ${REGISTRY_HOST}/${svc}:${ver}" || true + # 清理各种前缀的镜像 + docker rmi "${svc}:${ver}" 2>/dev/null && echo " Removed: ${svc}:${ver}" || true + docker rmi "git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/${svc}:${ver}" 2>/dev/null && \ + echo " Removed: git.xiaoxiajianji.com/.../${svc}:${ver}" || true done else echo "[${svc}] ${total} version(s) found, within keep limit (${KEEP_VERSIONS})" @@ -53,11 +46,17 @@ done # 清理悬空镜像(构建中间层) echo "=== Pruning dangling images ===" -pruned=$(docker image prune -f 2>&1) -echo "$pruned" | tail -1 +docker image prune -f 2>&1 | tail -1 + +# 清理未使用的构建缓存 +echo "" +echo "=== Pruning build cache ===" +docker builder prune -f 2>&1 | tail -1 || true echo "" echo "=== Cleanup complete ===" -echo "Current images:" -docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep -E "(xiaoxia|REPOSITORY)" || true - +echo "Current xiaoxia images:" +docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" 2>/dev/null | grep -E "(xiaoxia|REPOSITORY)" || echo " (none)" +echo "" +echo "Disk usage:" +df -h / | tail -1 -- 2.54.0 From 5f3e034f98e2c123b4015f9f28f579b02f8b6d8c Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:51:50 +0800 Subject: [PATCH 09/19] =?UTF-8?q?ci(P1-3):=20staging=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0smoke=20test=E5=A4=B1=E8=B4=A5=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=9B=9E=E6=BB=9A=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 51 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 2b7373ebb..9d218f7af 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -435,6 +435,7 @@ jobs: scripts/build_release_images.sh "${GITHUB_SHA}" staging - name: Tag and push :staging images (Watchtower auto-update) + id: tag-staging shell: sh env: REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} @@ -444,6 +445,20 @@ jobs: if [ -n "${REGISTRY_TOKEN:-}" ]; then printf '%s' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin 2>/dev/null fi + + # 保存当前 :staging 镜像的 digest 用于回滚 + ROLLBACK_DIGESTS="" + for svc in api worker web; do + OLD_DIGEST="" + if docker manifest inspect "${REGISTRY}/xiaoxia-saas-${svc}:staging" >/dev/null 2>&1; then + OLD_DIGEST=$(docker manifest inspect "${REGISTRY}/xiaoxia-saas-${svc}:staging" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('digest',''))" 2>/dev/null || true) + fi + ROLLBACK_DIGESTS="${ROLLBACK_DIGESTS}${svc}:${OLD_DIGEST};" + echo " ${svc}: previous staging digest = ${OLD_DIGEST:-none}" + done + echo "rollback_digests=${ROLLBACK_DIGESTS}" >> "$GITHUB_OUTPUT" + + # 推送新的 :staging tag for svc in api worker web; do docker tag "${REGISTRY}/xiaoxia-saas-${svc}:${GITHUB_SHA}" "${REGISTRY}/xiaoxia-saas-${svc}:staging" docker push "${REGISTRY}/xiaoxia-saas-${svc}:staging" @@ -452,8 +467,36 @@ jobs: - name: Wait for Watchtower update + smoke test shell: sh + env: + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + ROLLBACK_DIGESTS: ${{ steps.tag-staging.outputs.rollback_digests }} run: | set -eu + REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" + + rollback_staging() { + echo "" + echo "❌ Smoke test failed! Rolling back :staging tag to previous version..." + if [ -n "${REGISTRY_TOKEN:-}" ]; then + printf '%s' "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin 2>/dev/null || true + fi + # 解析保存的 digest 并回滚 + echo "$ROLLBACK_DIGESTS" | tr ';' ' +' | while IFS=':' read -r svc digest; do + [ -z "$svc" ] && continue + if [ -n "$digest" ]; then + echo " Rolling back ${svc} to digest ${digest:0:24}..." + docker buildx imagetools create -t "${REGISTRY}/xiaoxia-saas-${svc}:staging" "${REGISTRY}/xiaoxia-saas-${svc}@${digest}" 2>/dev/null || echo " ⚠️ Rollback of ${svc} failed, manual intervention needed" + else + echo " ⚠️ No previous digest for ${svc}, cannot roll back" + fi + done + echo "" + echo "⚠️ :staging tag has been rolled back. Watchtower will revert within 60s." + echo " Please investigate the smoke test failure for commit ${GITHUB_SHA}" + exit 1 + } + echo "Waiting 90s for Watchtower to detect new image and restart containers..." sleep 90 @@ -465,7 +508,7 @@ jobs: done if [ -z "$HEALTH" ]; then echo "FAIL: health endpoint unreachable after 60s" - exit 1 + rollback_staging fi echo "Health OK: $HEALTH" @@ -476,7 +519,7 @@ jobs: -d '{"email":"smoke@test.com","password":"wrong"}') if [ "$HTTP_CODE" != "401" ] && [ "$HTTP_CODE" != "422" ]; then echo "FAIL: login returned HTTP $HTTP_CODE (expected 401 or 422)" - exit 1 + rollback_staging fi echo "Login API OK: HTTP $HTTP_CODE" @@ -484,7 +527,7 @@ jobs: HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://staging-api.xiaoxiajianji.com/docs) if [ "$HTTP_CODE" != "200" ]; then echo "FAIL: /docs returned HTTP $HTTP_CODE (expected 200)" - exit 1 + rollback_staging fi echo "Docs endpoint OK: HTTP $HTTP_CODE" @@ -492,7 +535,7 @@ jobs: HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://staging.xiaoxiajianji.com/) if [ "$HTTP_CODE" != "200" ]; then echo "FAIL: web frontend returned HTTP $HTTP_CODE (expected 200)" - exit 1 + rollback_staging fi echo "Web frontend OK: HTTP $HTTP_CODE" -- 2.54.0 From 69c4ad32dcc9be9942bb867c241ffa5996262ca7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:55:17 +0800 Subject: [PATCH 10/19] =?UTF-8?q?ci(P1-4):=20=E7=94=9F=E4=BA=A7=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=A2=9E=E5=8A=A0=E5=A4=B1=E8=B4=A5=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=9B=9E=E6=BB=9A+=E4=BC=98=E9=9B=85=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=EF=BC=8C=E6=9C=80=E5=B0=8F=E5=8C=96=E6=9C=8D=E5=8A=A1=E4=B8=AD?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 9d218f7af..c071ed7bb 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -818,7 +818,7 @@ jobs: echo "SSH connection verified" # Registry 方式部署脚本(base64 编码避免转义问题) - DEPLOY_B64="IyEvYmluL3NoCnNldCAtZXUKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBQcm9kdWN0aW9uIOmDqOe9suiEmuacrCAtIFJlZ2lzdHJ5IOaWueW8jwojIOeUqOazle+8mklNQUdFX1RBRz08dmVyc2lvbj4gUkVHSVNUUllfVE9LRU49PHRva2VuPiBzaCBkZXBsb3ktcHJvZHVjdGlvbi1yZWdpc3RyeS5zaAojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpJTUFHRV9UQUc9IiR7SU1BR0VfVEFHOi19IgpSRUdJU1RSWT0iJHtSRUdJU1RSWTotZ2l0LnhpYW94aWFqaWFuamkuY29tL3hpYW94aWEveGlhb3hpYS1zYWFzfSIKUkVHSVNUUllfVVNFUj0iJHtSRUdJU1RSWV9VU0VSOi14aWFveGlhfSIKUkVHSVNUUllfVE9LRU49IiR7UkVHSVNUUllfVE9LRU46LX0iCgpFTlZfRklMRT0iJHtFTlZfRklMRTotL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vLmVudn0iCkdFTkVSQVRFRF9ESVI9IiR7R0VORVJBVEVEX0RJUjotL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vZ2VuZXJhdGVkfSIKTEVHQUNZX0FTU0VUU19ESVI9IiR7TEVHQUNZX0FTU0VUU19ESVI6LS92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL2xlZ2FjeS1hc3NldHN9IgoKaWYgWyAteiAiJElNQUdFX1RBRyIgXTsgdGhlbgogIGVjaG8gIkVSUk9SOiBJTUFHRV9UQUcgaXMgcmVxdWlyZWQiCiAgZXhpdCAxCmZpCgp0ZXN0IC1mICIkRU5WX0ZJTEUiCm1rZGlyIC1wICIkR0VORVJBVEVEX0RJUiIKbWtkaXIgLXAgIiRMRUdBQ1lfQVNTRVRTX0RJUiIKCiMgLS0tLSDnmbvlvZUgUmVnaXN0cnkgLS0tLQppZiBbIC1uICIkUkVHSVNUUllfVE9LRU4iIF07IHRoZW4KICBlY2hvICJMb2dnaW5nIGluIHRvIHJlZ2lzdHJ5OiAkUkVHSVNUUlkiCiAgUkVHSVNUUllfSE9TVD0kKGVjaG8gIiRSRUdJU1RSWSIgfCBjdXQgLWQvIC1mMSkKICBwcmludGYgJXMgIiRSRUdJU1RSWV9UT0tFTiIgfCBkb2NrZXIgbG9naW4gIiRSRUdJU1RSWV9IT1NUIiAtdSAiJFJFR0lTVFJZX1VTRVIiIC0tcGFzc3dvcmQtc3RkaW4gMj4vZGV2L251bGwgfHwgewogICAgZWNobyAiV0FSTjogZG9ja2VyIGxvZ2luIGZhaWxlZCwgd2lsbCB0cnkgdG8gcHVsbCBhbnl3YXkiCiAgfQpmaQoKIyAtLS0tIFB1bGwg5LiJ6ZWc5YOPIC0tLS0KUkVHSVNUUllfQVBJPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtYXBpOiR7SU1BR0VfVEFHfSIKUkVHSVNUUllfV09SS0VSPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtd29ya2VyOiR7SU1BR0VfVEFHfSIKUkVHSVNUUllfV0VCPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtd2ViOiR7SU1BR0VfVEFHfSIKCkxPQ0FMX0FQST0ieGlhb3hpYS1zYWFzLWFwaToke0lNQUdFX1RBR30iCkxPQ0FMX1dPUktFUj0ieGlhb3hpYS1zYWFzLXdvcmtlcjoke0lNQUdFX1RBR30iCkxPQ0FMX1dFQj0ieGlhb3hpYS1zYWFzLXdlYjoke0lNQUdFX1RBR30iCgplY2hvICJQdWxsaW5nIEFQSSBpbWFnZS4uLiIKZG9ja2VyIHB1bGwgIiRSRUdJU1RSWV9BUEkiCmVjaG8gIlB1bGxpbmcgV29ya2VyIGltYWdlLi4uIgpkb2NrZXIgcHVsbCAiJFJFR0lTVFJZX1dPUktFUiIKZWNobyAiUHVsbGluZyBXZWIgaW1hZ2UuLi4iCmRvY2tlciBwdWxsICIkUkVHSVNUUllfV0VCIgoKIyAtLS0tIFJlLXRhZyDmiJDmnKzlnLDlkI0gLS0tLQpkb2NrZXIgdGFnICIkUkVHSVNUUllfQVBJIiAiJExPQ0FMX0FQSSIKZG9ja2VyIHRhZyAiJFJFR0lTVFJZX1dPUktFUiIgIiRMT0NBTF9XT1JLRVIiCmRvY2tlciB0YWcgIiRSRUdJU1RSWV9XRUIiICIkTE9DQUxfV0VCIgplY2hvICJBbGwgaW1hZ2VzIHB1bGxlZCBhbmQgdGFnZ2VkLiIKCiMgLS0tLSDlpIfku73ml6fniYggYXNzZXRz77yI6YOo572y5pyf6Ze057yT5a2Y55So5oi35LiNIDQwNO+8iSAtLS0tCmVjaG8gIkJhY2tpbmcgdXAgbGVnYWN5IGFzc2V0cyBmcm9tIGN1cnJlbnQgd2ViIGNvbnRhaW5lci4uLiIKaWYgZG9ja2VyIGluc3BlY3QgeGlhb3hpYS13ZWItcHJvZHVjdGlvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICBfdG1wZGlyPSIvdG1wL2xlZ2FjeS1hc3NldHMtJCQiCiAgcm0gLXJmICIkX3RtcGRpciIKICBta2RpciAtcCAiJF90bXBkaXIiCiAgZG9ja2VyIGNwIHhpYW94aWEtd2ViLXByb2R1Y3Rpb246L3Vzci9zaGFyZS9uZ2lueC9odG1sL2Fzc2V0cy8uICIkX3RtcGRpci8iIDI+L2Rldi9udWxsIHx8IHRydWUKICAjIOWQiOW5tuWIsCBMRUdBQ1lfQVNTRVRTX0RJUu+8iOS/neeVmeaJgOacieWOhuWPsueJiOacrOeahCBhc3NldHPvvIkKICBpZiBbIC1kICIkX3RtcGRpciIgXSAmJiBbICIkKGxzIC1BICIkX3RtcGRpciIgMj4vZGV2L251bGwpIiBdOyB0aGVuCiAgICBjcCAtYW4gIiRfdG1wZGlyIi8uICIkTEVHQUNZX0FTU0VUU19ESVIiLyAyPi9kZXYvbnVsbCB8fCB0cnVlCiAgICBlY2hvICJMZWdhY3kgYXNzZXRzIGJhY2tlZCB1cDogJChscyAiJF90bXBkaXIiIHwgd2MgLWwpIGZpbGVzIgogIGZpCiAgcm0gLXJmICIkX3RtcGRpciIKZWxzZQogIGVjaG8gIk5vIGV4aXN0aW5nIHdlYiBjb250YWluZXIsIHNraXBwaW5nIGxlZ2FjeSBhc3NldHMgYmFja3VwIgpmaQoKIyDmuIXnkIbotoXov4cgNyDlpKnnmoTml6cgYXNzZXRzIOaWh+S7tu+8iOmBv+WFjeaXoOmZkOWinumVv++8iQppZiBbIC1kICIkTEVHQUNZX0FTU0VUU19ESVIiIF07IHRoZW4KICBmaW5kICIkTEVHQUNZX0FTU0VUU19ESVIiIC10eXBlIGYgLW10aW1lICs3IC1kZWxldGUgMj4vZGV2L251bGwgfHwgdHJ1ZQogIGVjaG8gIkxlZ2FjeSBhc3NldHMgY2xlYW51cCBkb25lIChyZXRhaW4gNyBkYXlzKSIKZmkKCiMgLS0tLSDnoa7kv53ln7rnoYDorr7mlr3lrrnlmajlnKjov5DooYwgLS0tLQplY2hvICJDaGVja2luZyBpbmZyYXN0cnVjdHVyZSBjb250YWluZXJzLi4uIgpmb3IgYyBpbiB4aWFveGlhLXBvc3RncmVzLXByb2R1Y3Rpb24geGlhb3hpYS1yZWRpcy1wcm9kdWN0aW9uOyBkbwogIGlmICEgZG9ja2VyIGluc3BlY3QgIiRjIiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIkVSUk9SOiBSZXF1aXJlZCBjb250YWluZXIgbm90IGZvdW5kOiAkYyIKICAgIGV4aXQgMQogIGZpCiAgc3RhdGU9JChkb2NrZXIgaW5zcGVjdCAtZiAne3suU3RhdGUuU3RhdHVzfX0nICIkYyIpCiAgaWYgWyAiJHN0YXRlIiAhPSAicnVubmluZyIgXTsgdGhlbgogICAgZWNobyAiRVJST1I6IENvbnRhaW5lciBub3QgcnVubmluZzogJGMgKCRzdGF0ZSkiCiAgICBleGl0IDEKICBmaQpkb25lCgojIC0tLS0g56Gu5L+d55Sf5Lqn572R57uc5a2Y5ZyoIC0tLS0KZG9ja2VyIG5ldHdvcmsgY3JlYXRlIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gMj4vZGV2L251bGwgfHwgdHJ1ZQoKIyAtLS0tIOaJp+ihjOaVsOaNruW6kyBNaWdyYXRpb24gLS0tLQplY2hvICJSdW5uaW5nIGRhdGFiYXNlIG1pZ3JhdGlvbnMuLi4iCmRvY2tlciBydW4gLS1ybSBcCiAgLS1lbnYtZmlsZSAiJEVOVl9GSUxFIiBcCiAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gXAogIC1lIEFQUF9FTlY9cHJvZHVjdGlvbiBcCiAgIiRMT0NBTF9BUEkiIHNoIC1jICJjZCAvYXBwICYmIGFsZW1iaWMgdXBncmFkZSBoZWFkIgplY2hvICJNaWdyYXRpb25zIGNvbXBsZXRlZC4iCgojIC0tLS0g5YGc5q2i5pen5a655ZmoIC0tLS0KZWNobyAiU3RvcHBpbmcgb2xkIGNvbnRhaW5lcnMuLi4iCmRvY2tlciBybSAtZiB4aWFveGlhLWFwaS1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKZG9ja2VyIHJtIC1mIHhpYW94aWEtd29ya2VyLXByb2R1Y3Rpb24gMj4vZGV2L251bGwgfHwgdHJ1ZQpkb2NrZXIgcm0gLWYgeGlhb3hpYS13ZWItcHJvZHVjdGlvbiAyPi9kZXYvbnVsbCB8fCB0cnVlCgojIC0tLS0g5pel5b+X6YWN572u77yI5omA5pyJ5a655Zmo5YWx55So77yJIC0tLS0KTE9HX09QVFM9Ii0tbG9nLWRyaXZlciBqc29uLWZpbGUgLS1sb2ctb3B0IG1heC1zaXplPTUwbSAtLWxvZy1vcHQgbWF4LWZpbGU9MyIKCiMgLS0tLSDlkK/liqggQVBJIC0tLS0KZWNobyAiU3RhcnRpbmcgQVBJIGNvbnRhaW5lci4uLiIKZG9ja2VyIHJ1biAtZCBcCiAgLS1uYW1lIHhpYW94aWEtYXBpLXByb2R1Y3Rpb24gXAogIC0tZW52LWZpbGUgIiRFTlZfRklMRSIgXAogIC0tbmV0d29yayB4aWFveGlhLW5ldC1wcm9kdWN0aW9uIFwKICAtcCAxMjcuMC4wLjE6ODAwMTo4MDAwIFwKICAtZSBBUFBfRU5WPXByb2R1Y3Rpb24gXAogIC1lIEFQUF9WRVJTSU9OPSIkSU1BR0VfVEFHIiBcCiAgLWUgR0VORVJBVEVEX0ZJTEVTX0RJUj0vYXBwL2dlbmVyYXRlZCBcCiAgLWUgR0VORVJBVEVEX0ZJTEVTX1VSTF9QUkVGSVg9L2dlbmVyYXRlZC1maWxlcyBcCiAgLWUgUFVCTElDX0FQSV9CQVNFX1VSTD1odHRwczovL2FwaS54aWFveGlhamlhbmppLmNvbSBcCiAgLXYgIiRHRU5FUkFURURfRElSOi9hcHAvZ2VuZXJhdGVkIiBcCiAgLS1yZXN0YXJ0IHVubGVzcy1zdG9wcGVkIFwKICAtLWNwdXMgMiBcCiAgLS1tZW1vcnkgMmcgXAogIC0taGVhbHRoLWNtZCAicHl0aG9uIC1jIFwiaW1wb3J0IHVybGxpYi5yZXF1ZXN0OyB1cmxsaWIucmVxdWVzdC51cmxvcGVuKCdodHRwOi8vbG9jYWxob3N0OjgwMDAvaGVhbHRoJywgdGltZW91dD01KVwiIiBcCiAgLS1oZWFsdGgtaW50ZXJ2YWwgMzBzIFwKICAtLWhlYWx0aC10aW1lb3V0IDEwcyBcCiAgLS1oZWFsdGgtcmV0cmllcyAzIFwKICAtLWhlYWx0aC1zdGFydC1wZXJpb2QgNDBzIFwKICAkTE9HX09QVFMgXAogICIkTE9DQUxfQVBJIgoKIyAtLS0tIOWQr+WKqCBXb3JrZXIgLS0tLQplY2hvICJTdGFydGluZyBXb3JrZXIgY29udGFpbmVyLi4uIgpkb2NrZXIgcnVuIC1kIFwKICAtLW5hbWUgeGlhb3hpYS13b3JrZXItcHJvZHVjdGlvbiBcCiAgLS1lbnYtZmlsZSAiJEVOVl9GSUxFIiBcCiAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gXAogIC1lIEFQUF9FTlY9cHJvZHVjdGlvbiBcCiAgLWUgQVBQX1ZFUlNJT049IiRJTUFHRV9UQUciIFwKICAtZSBXT1JLRVJfQ09OQ1VSUkVOQ1k9MSBcCiAgLWUgV09SS0VSX01BWF9UQVNLU19QRVJfQ0hJTEQ9MTAwIFwKICAtZSBHRU5FUkFURURfRklMRVNfRElSPS9hcHAvZ2VuZXJhdGVkIFwKICAtZSBHRU5FUkFURURfRklMRVNfVVJMX1BSRUZJWD0vZ2VuZXJhdGVkLWZpbGVzIFwKICAtZSBQVUJMSUNfQVBJX0JBU0VfVVJMPWh0dHBzOi8vYXBpLnhpYW94aWFqaWFuamkuY29tIFwKICAtdiAiJEdFTkVSQVRFRF9ESVI6L2FwcC9nZW5lcmF0ZWQiIFwKICAtLXJlc3RhcnQgdW5sZXNzLXN0b3BwZWQgXAogIC0tY3B1cyAyIFwKICAtLW1lbW9yeSAyZyBcCiAgLS1oZWFsdGgtY21kICJzaCAtYyBcImdyZXAgLXEgY2VsZXJ5IC9wcm9jLzEvY21kbGluZSB8fCBleGl0IDFcIiIgXAogIC0taGVhbHRoLWludGVydmFsIDMwcyBcCiAgLS1oZWFsdGgtdGltZW91dCAxMHMgXAogIC0taGVhbHRoLXJldHJpZXMgMyBcCiAgLS1oZWFsdGgtc3RhcnQtcGVyaW9kIDMwcyBcCiAgJExPR19PUFRTIFwKICAiJExPQ0FMX1dPUktFUiIKCiMgLS0tLSDlkK/liqggV2ViIC0tLS0KIyBMZWdhY3kgYXNzZXRzIOaMgui9veWIsCAvdXNyL3NoYXJlL25naW54L2h0bWwvYXNzZXRzLWxlZ2FjeS9hc3NldHMvCiMgbmdpbngg6YWN572u5LitIGFzc2V0cyBsb2NhdGlvbiDmnIkgZmFsbGJhY2sg6YC76L6RCkxFR0FDWV9WT0xVTUU9IiIKaWYgWyAtZCAiJExFR0FDWV9BU1NFVFNfRElSIiBdICYmIFsgIiQobHMgLUEgIiRMRUdBQ1lfQVNTRVRTX0RJUiIgMj4vZGV2L251bGwpIiBdOyB0aGVuCiAgTEVHQUNZX1ZPTFVNRT0iLXYgJHtMRUdBQ1lfQVNTRVRTX0RJUn06L3Vzci9zaGFyZS9uZ2lueC9odG1sL2Fzc2V0cy1sZWdhY3kvYXNzZXRzOnJvIgogIGVjaG8gIldlYiBjb250YWluZXI6IGxlZ2FjeSBhc3NldHMgbW91bnRlZCAoZmFsbGJhY2spIgplbHNlCiAgZWNobyAiV2ViIGNvbnRhaW5lcjogbm8gbGVnYWN5IGFzc2V0cyB0byBtb3VudCIKZmkKCmVjaG8gIlN0YXJ0aW5nIFdlYiBjb250YWluZXIuLi4iCmRvY2tlciBydW4gLWQgXAogIC0tbmFtZSB4aWFveGlhLXdlYi1wcm9kdWN0aW9uIFwKICAtLW5ldHdvcmsgeGlhb3hpYS1uZXQtcHJvZHVjdGlvbiBcCiAgLXAgMTI3LjAuMC4xOjMwMDI6ODAgXAogIC0tcmVzdGFydCB1bmxlc3Mtc3RvcHBlZCBcCiAgLS1jcHVzIDAuNSBcCiAgLS1tZW1vcnkgNTEybSBcCiAgJExFR0FDWV9WT0xVTUUgXAogIC0taGVhbHRoLWNtZCAid2dldCAtLXNwaWRlciAtcSBodHRwOi8vMTI3LjAuMC4xOjgwIiBcCiAgLS1oZWFsdGgtaW50ZXJ2YWwgMzBzIFwKICAtLWhlYWx0aC10aW1lb3V0IDVzIFwKICAtLWhlYWx0aC1yZXRyaWVzIDMgXAogICRMT0dfT1BUUyBcCiAgIiRMT0NBTF9XRUIiCgojIC0tLS0g562J5b6FIEFQSSDlgaXlurcgLS0tLQplY2hvICJXYWl0aW5nIGZvciBBUEkgdG8gYmVjb21lIGhlYWx0aHkuLi4iCmk9MAp3aGlsZSBbICIkaSIgLWx0IDQwIF07IGRvCiAgaWYgY3VybCAtc2YgLS1tYXgtdGltZSA1IGh0dHA6Ly8xMjcuMC4wLjE6ODAwMS9oZWFsdGggPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICBlY2hvICJBUEkgaXMgaGVhbHRoeSEiCiAgICBicmVhawogIGZpCiAgaT0kKChpICsgMSkpCiAgZWNobyAiICBXYWl0aW5nLi4uICgkaS80MCkiCiAgc2xlZXAgMwpkb25lCgppZiBbICIkaSIgLWdlIDQwIF07IHRoZW4KICBlY2hvICJFUlJPUjogQVBJIGRpZCBub3QgYmVjb21lIGhlYWx0aHkgd2l0aGluIDEyMHMiCiAgZG9ja2VyIGxvZ3MgLS10YWlsIDUwIHhpYW94aWEtYXBpLXByb2R1Y3Rpb24KICBleGl0IDEKZmkKCiMgLS0tLSDnrYnlvoUgV2ViIOWBpeW6tyAtLS0tCmVjaG8gIldhaXRpbmcgZm9yIFdlYiB0byBiZWNvbWUgaGVhbHRoeS4uLiIKaT0wCndoaWxlIFsgIiRpIiAtbHQgMTUgXTsgZG8KICBpZiBjdXJsIC1zZiAtLW1heC10aW1lIDUgaHR0cDovLzEyNy4wLjAuMTozMDAyLyA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIldlYiBpcyBoZWFsdGh5ISIKICAgIGJyZWFrCiAgZmkKICBpPSQoKGkgKyAxKSkKICBlY2hvICIgIFdhaXRpbmcuLi4gKCRpLzE1KSIKICBzbGVlcCAyCmRvbmUKCmlmIFsgIiRpIiAtZ2UgMTUgXTsgdGhlbgogIGVjaG8gIkVSUk9SOiBXZWIgZGlkIG5vdCBiZWNvbWUgaGVhbHRoeSB3aXRoaW4gMzBzIgogIGRvY2tlciBsb2dzIC0tdGFpbCAzMCB4aWFveGlhLXdlYi1wcm9kdWN0aW9uCiAgZXhpdCAxCmZpCgojIC0tLS0g5riF55CG5pen6ZWc5YOPIC0tLS0KZWNobyAiQ2xlYW5pbmcgdXAgb2xkIGltYWdlcy4uLiIKZG9ja2VyIGltYWdlIHBydW5lIC1hZiAtLWZpbHRlciAidW50aWw9MTY4aCIgMj4vZGV2L251bGwgfHwgdHJ1ZQpkb2NrZXIgYnVpbGRlciBwcnVuZSAtYWYgLS1maWx0ZXIgInVudGlsPTE2OGgiIDI+L2Rldi9udWxsIHx8IHRydWUKCmVjaG8gIiIKZWNobyAiPT09IFByb2R1Y3Rpb24gZGVwbG95bWVudCBjb21wbGV0ZSA9PT0iCmVjaG8gIkFQSTogICAgICBodHRwOi8vMTI3LjAuMC4xOjgwMDEiCmVjaG8gIldlYjogICAgICBodHRwOi8vMTI3LjAuMC4xOjMwMDIiCmVjaG8gIlZlcnNpb246ICAkSU1BR0VfVEFHIgpkb2NrZXIgcHMgLS1mb3JtYXQgInRhYmxlIHt7Lk5hbWVzfX1cdHt7LlN0YXR1c319XHR7ey5JbWFnZX19IiB8IGdyZXAgcHJvZHVjdGlvbgo=" + DEPLOY_B64="IyEvYmluL3NoCnNldCAtZXUKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBQcm9kdWN0aW9uIOmDqOe9suiEmuacrCAtIFJlZ2lzdHJ5IOaWueW8jwojIOeUqOazle+8mklNQUdFX1RBRz08dmVyc2lvbj4gUkVHSVNUUllfVE9LRU49PHRva2VuPiBzaCBkZXBsb3ktcHJvZHVjdGlvbi1yZWdpc3RyeS5zaAojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpJTUFHRV9UQUc9IiR7SU1BR0VfVEFHOi19IgpSRUdJU1RSWT0iJHtSRUdJU1RSWTotZ2l0LnhpYW94aWFqaWFuamkuY29tL3hpYW94aWEveGlhb3hpYS1zYWFzfSIKUkVHSVNUUllfVVNFUj0iJHtSRUdJU1RSWV9VU0VSOi14aWFveGlhfSIKUkVHSVNUUllfVE9LRU49IiR7UkVHSVNUUllfVE9LRU46LX0iCgpFTlZfRklMRT0iJHtFTlZfRklMRTotL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vLmVudn0iCkdFTkVSQVRFRF9ESVI9IiR7R0VORVJBVEVEX0RJUjotL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vZ2VuZXJhdGVkfSIKTEVHQUNZX0FTU0VUU19ESVI9IiR7TEVHQUNZX0FTU0VUU19ESVI6LS92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL2xlZ2FjeS1hc3NldHN9IgoKaWYgWyAteiAiJElNQUdFX1RBRyIgXTsgdGhlbgogIGVjaG8gIkVSUk9SOiBJTUFHRV9UQUcgaXMgcmVxdWlyZWQiCiAgZXhpdCAxCmZpCgp0ZXN0IC1mICIkRU5WX0ZJTEUiCm1rZGlyIC1wICIkR0VORVJBVEVEX0RJUiIKbWtkaXIgLXAgIiRMRUdBQ1lfQVNTRVRTX0RJUiIKCiMgLS0tLSDnmbvlvZUgUmVnaXN0cnkgLS0tLQppZiBbIC1uICIkUkVHSVNUUllfVE9LRU4iIF07IHRoZW4KICBlY2hvICJMb2dnaW5nIGluIHRvIHJlZ2lzdHJ5OiAkUkVHSVNUUlkiCiAgUkVHSVNUUllfSE9TVD0kKGVjaG8gIiRSRUdJU1RSWSIgfCBjdXQgLWQvIC1mMSkKICBwcmludGYgJXMgIiRSRUdJU1RSWV9UT0tFTiIgfCBkb2NrZXIgbG9naW4gIiRSRUdJU1RSWV9IT1NUIiAtdSAiJFJFR0lTVFJZX1VTRVIiIC0tcGFzc3dvcmQtc3RkaW4gMj4vZGV2L251bGwgfHwgewogICAgZWNobyAiV0FSTjogZG9ja2VyIGxvZ2luIGZhaWxlZCwgd2lsbCB0cnkgdG8gcHVsbCBhbnl3YXkiCiAgfQpmaQoKIyAtLS0tIFB1bGwg5LiJ6ZWc5YOPIC0tLS0KUkVHSVNUUllfQVBJPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtYXBpOiR7SU1BR0VfVEFHfSIKUkVHSVNUUllfV09SS0VSPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtd29ya2VyOiR7SU1BR0VfVEFHfSIKUkVHSVNUUllfV0VCPSIke1JFR0lTVFJZfS94aWFveGlhLXNhYXMtd2ViOiR7SU1BR0VfVEFHfSIKCkxPQ0FMX0FQST0ieGlhb3hpYS1zYWFzLWFwaToke0lNQUdFX1RBR30iCkxPQ0FMX1dPUktFUj0ieGlhb3hpYS1zYWFzLXdvcmtlcjoke0lNQUdFX1RBR30iCkxPQ0FMX1dFQj0ieGlhb3hpYS1zYWFzLXdlYjoke0lNQUdFX1RBR30iCgplY2hvICJQdWxsaW5nIEFQSSBpbWFnZS4uLiIKZG9ja2VyIHB1bGwgIiRSRUdJU1RSWV9BUEkiCmVjaG8gIlB1bGxpbmcgV29ya2VyIGltYWdlLi4uIgpkb2NrZXIgcHVsbCAiJFJFR0lTVFJZX1dPUktFUiIKZWNobyAiUHVsbGluZyBXZWIgaW1hZ2UuLi4iCmRvY2tlciBwdWxsICIkUkVHSVNUUllfV0VCIgoKIyAtLS0tIFJlLXRhZyDmiJDmnKzlnLDlkI0gLS0tLQpkb2NrZXIgdGFnICIkUkVHSVNUUllfQVBJIiAiJExPQ0FMX0FQSSIKZG9ja2VyIHRhZyAiJFJFR0lTVFJZX1dPUktFUiIgIiRMT0NBTF9XT1JLRVIiCmRvY2tlciB0YWcgIiRSRUdJU1RSWV9XRUIiICIkTE9DQUxfV0VCIgplY2hvICJBbGwgaW1hZ2VzIHB1bGxlZCBhbmQgdGFnZ2VkLiIKCiMgLS0tLSDlpIfku73ml6fniYggYXNzZXRz77yI6YOo572y5pyf6Ze057yT5a2Y55So5oi35LiNIDQwNO+8iSAtLS0tCmVjaG8gIkJhY2tpbmcgdXAgbGVnYWN5IGFzc2V0cyBmcm9tIGN1cnJlbnQgd2ViIGNvbnRhaW5lci4uLiIKaWYgZG9ja2VyIGluc3BlY3QgeGlhb3hpYS13ZWItcHJvZHVjdGlvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICBfdG1wZGlyPSIvdG1wL2xlZ2FjeS1hc3NldHMtJCQiCiAgcm0gLXJmICIkX3RtcGRpciIKICBta2RpciAtcCAiJF90bXBkaXIiCiAgZG9ja2VyIGNwIHhpYW94aWEtd2ViLXByb2R1Y3Rpb246L3Vzci9zaGFyZS9uZ2lueC9odG1sL2Fzc2V0cy8uICIkX3RtcGRpci8iIDI+L2Rldi9udWxsIHx8IHRydWUKICAjIOWQiOW5tuWIsCBMRUdBQ1lfQVNTRVRTX0RJUu+8iOS/neeVmeaJgOacieWOhuWPsueJiOacrOeahCBhc3NldHPvvIkKICBpZiBbIC1kICIkX3RtcGRpciIgXSAmJiBbICIkKGxzIC1BICIkX3RtcGRpciIgMj4vZGV2L251bGwpIiBdOyB0aGVuCiAgICBjcCAtYW4gIiRfdG1wZGlyIi8uICIkTEVHQUNZX0FTU0VUU19ESVIiLyAyPi9kZXYvbnVsbCB8fCB0cnVlCiAgICBlY2hvICJMZWdhY3kgYXNzZXRzIGJhY2tlZCB1cDogJChscyAiJF90bXBkaXIiIHwgd2MgLWwpIGZpbGVzIgogIGZpCiAgcm0gLXJmICIkX3RtcGRpciIKZWxzZQogIGVjaG8gIk5vIGV4aXN0aW5nIHdlYiBjb250YWluZXIsIHNraXBwaW5nIGxlZ2FjeSBhc3NldHMgYmFja3VwIgpmaQoKIyDmuIXnkIbotoXov4cgNyDlpKnnmoTml6cgYXNzZXRzIOaWh+S7tu+8iOmBv+WFjeaXoOmZkOWinumVv++8iQppZiBbIC1kICIkTEVHQUNZX0FTU0VUU19ESVIiIF07IHRoZW4KICBmaW5kICIkTEVHQUNZX0FTU0VUU19ESVIiIC10eXBlIGYgLW10aW1lICs3IC1kZWxldGUgMj4vZGV2L251bGwgfHwgdHJ1ZQogIGVjaG8gIkxlZ2FjeSBhc3NldHMgY2xlYW51cCBkb25lIChyZXRhaW4gNyBkYXlzKSIKZmkKCiMgLS0tLSDnoa7kv53ln7rnoYDorr7mlr3lrrnlmajlnKjov5DooYwgLS0tLQplY2hvICJDaGVja2luZyBpbmZyYXN0cnVjdHVyZSBjb250YWluZXJzLi4uIgpmb3IgYyBpbiB4aWFveGlhLXBvc3RncmVzLXByb2R1Y3Rpb24geGlhb3hpYS1yZWRpcy1wcm9kdWN0aW9uOyBkbwogIGlmICEgZG9ja2VyIGluc3BlY3QgIiRjIiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIkVSUk9SOiBSZXF1aXJlZCBjb250YWluZXIgbm90IGZvdW5kOiAkYyIKICAgIGV4aXQgMQogIGZpCiAgc3RhdGU9JChkb2NrZXIgaW5zcGVjdCAtZiAne3suU3RhdGUuU3RhdHVzfX0nICIkYyIpCiAgaWYgWyAiJHN0YXRlIiAhPSAicnVubmluZyIgXTsgdGhlbgogICAgZWNobyAiRVJST1I6IENvbnRhaW5lciBub3QgcnVubmluZzogJGMgKCRzdGF0ZSkiCiAgICBleGl0IDEKICBmaQpkb25lCgojIC0tLS0g56Gu5L+d55Sf5Lqn572R57uc5a2Y5ZyoIC0tLS0KZG9ja2VyIG5ldHdvcmsgY3JlYXRlIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gMj4vZGV2L251bGwgfHwgdHJ1ZQoKIyAtLS0tIOaJp+ihjOaVsOaNruW6kyBNaWdyYXRpb24gLS0tLQplY2hvICJSdW5uaW5nIGRhdGFiYXNlIG1pZ3JhdGlvbnMuLi4iCmRvY2tlciBydW4gLS1ybSBcCiAgLS1lbnYtZmlsZSAiJEVOVl9GSUxFIiBcCiAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gXAogIC1lIEFQUF9FTlY9cHJvZHVjdGlvbiBcCiAgIiRMT0NBTF9BUEkiIHNoIC1jICJjZCAvYXBwICYmIGFsZW1iaWMgdXBncmFkZSBoZWFkIgplY2hvICJNaWdyYXRpb25zIGNvbXBsZXRlZC4iCgojIC0tLS0g6K6w5b2V5b2T5YmN6L+Q6KGM54mI5pys77yI55So5LqO5Zue5rua77yJIC0tLS0KZ2V0X3J1bm5pbmdfaW1hZ2UoKSB7CiAgbG9jYWwgbmFtZT0kMQogIGRvY2tlciBpbnNwZWN0IC0tZm9ybWF0PSd7ey5Db25maWcuSW1hZ2V9fScgIiRuYW1lIiAyPi9kZXYvbnVsbCB8fCBlY2hvICIiCn0KT0xEX0FQSV9JTUFHRT0kKGdldF9ydW5uaW5nX2ltYWdlIHhpYW94aWEtYXBpLXByb2R1Y3Rpb24pCk9MRF9XT1JLRVJfSU1BR0U9JChnZXRfcnVubmluZ19pbWFnZSB4aWFveGlhLXdvcmtlci1wcm9kdWN0aW9uKQpPTERfV0VCX0lNQUdFPSQoZ2V0X3J1bm5pbmdfaW1hZ2UgeGlhb3hpYS13ZWItcHJvZHVjdGlvbikKZWNobyAiQ3VycmVudCBydW5uaW5nIHZlcnNpb25zOiIKZWNobyAiICBBUEk6ICAgICR7T0xEX0FQSV9JTUFHRTotbm9uZX0iCmVjaG8gIiAgV29ya2VyOiAke09MRF9XT1JLRVJfSU1BR0U6LW5vbmV9IgplY2hvICIgIFdlYjogICAgJHtPTERfV0VCX0lNQUdFOi1ub25lfSIKCiMg5Zue5rua5Ye95pWw77ya5aSx6LSl5pe26Ieq5Yqo5oGi5aSN5pen54mI5pysCnJvbGxiYWNrX3Byb2R1Y3Rpb24oKSB7CiAgZWNobyAiIgogIGVjaG8gIuKdjCBEZXBsb3ltZW50IGZhaWxlZCEgQXR0ZW1wdGluZyBhdXRvbWF0aWMgcm9sbGJhY2suLi4iCiAgZWNobyAiIgoKICAjIOa4heeQhuWksei0peeahOaWsOWuueWZqAogIGRvY2tlciBybSAtZiB4aWFveGlhLWFwaS1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKICBkb2NrZXIgcm0gLWYgeGlhb3hpYS13b3JrZXItcHJvZHVjdGlvbiAyPi9kZXYvbnVsbCB8fCB0cnVlCiAgZG9ja2VyIHJtIC1mIHhpYW94aWEtd2ViLXByb2R1Y3Rpb24gMj4vZGV2L251bGwgfHwgdHJ1ZQoKICBpZiBbIC1uICIkT0xEX0FQSV9JTUFHRSIgXTsgdGhlbgogICAgZWNobyAiUm9sbGluZyBiYWNrIEFQSSB0byAkT0xEX0FQSV9JTUFHRSAuLi4iCiAgICBkb2NrZXIgcnVuIC1kICAgICAgIC0tbmFtZSB4aWFveGlhLWFwaS1wcm9kdWN0aW9uICAgICAgIC0tZW52LWZpbGUgIiRFTlZfRklMRSIgICAgICAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gICAgICAgLXAgMTI3LjAuMC4xOjgwMDE6ODAwMCAgICAgICAtZSBBUFBfRU5WPXByb2R1Y3Rpb24gICAgICAgLWUgQVBQX1ZFUlNJT049InJvbGxiYWNrIiAgICAgICAtZSBHRU5FUkFURURfRklMRVNfRElSPS9hcHAvZ2VuZXJhdGVkICAgICAgIC1lIEdFTkVSQVRFRF9GSUxFU19VUkxfUFJFRklYPS9nZW5lcmF0ZWQtZmlsZXMgICAgICAgLWUgUFVCTElDX0FQSV9CQVNFX1VSTD1odHRwczovL2FwaS54aWFveGlhb2ppYW5qaS5jb20gICAgICAgLXYgIiRHRU5FUkFURURfRElSOi9hcHAvZ2VuZXJhdGVkIiAgICAgICAtLXJlc3RhcnQgdW5sZXNzLXN0b3BwZWQgICAgICAgLS1jcHVzIDIgICAgICAgLS1tZW1vcnkgMmcgICAgICAgLS1oZWFsdGgtY21kICJweXRob24gLWMgImltcG9ydCB1cmxsaWIucmVxdWVzdDsgdXJsbGliLnJlcXVlc3QudXJsb3BlbignaHR0cDovL2xvY2FsaG9zdDo4MDAwL2hlYWx0aCcsIHRpbWVvdXQ9NSkiIiAgICAgICAtLWhlYWx0aC1pbnRlcnZhbCAzMHMgICAgICAgLS1oZWFsdGgtdGltZW91dCAxMHMgICAgICAgLS1oZWFsdGgtcmV0cmllcyAzICAgICAgIC0taGVhbHRoLXN0YXJ0LXBlcmlvZCA0MHMgICAgICAgJExPR19PUFRTICAgICAgICIkT0xEX0FQSV9JTUFHRSIgJiYgZWNobyAiICBBUEkgcm9sbGJhY2sgc3RhcnRlZCIKICBlbHNlCiAgICBlY2hvICIgIOKaoO+4jyAgTm8gb2xkIEFQSSBpbWFnZSB0byByb2xsIGJhY2sgdG8iCiAgZmkKCiAgaWYgWyAtbiAiJE9MRF9XT1JLRVJfSU1BR0UiIF07IHRoZW4KICAgIGVjaG8gIlJvbGxpbmcgYmFjayBXb3JrZXIgdG8gJE9MRF9XT1JLRVJfSU1BR0UgLi4uIgogICAgZG9ja2VyIHJ1biAtZCAgICAgICAtLW5hbWUgeGlhb3hpYS13b3JrZXItcHJvZHVjdGlvbiAgICAgICAtLWVudi1maWxlICIkRU5WX0ZJTEUiICAgICAgIC0tbmV0d29yayB4aWFveGlhLW5ldC1wcm9kdWN0aW9uICAgICAgIC1lIEFQUF9FTlY9cHJvZHVjdGlvbiAgICAgICAtZSBBUFBfVkVSU0lPTj0icm9sbGJhY2siICAgICAgIC1lIFdPUktFUl9DT05DVVJSRU5DWT0xICAgICAgIC1lIFdPUktFUl9NQVhfVEFTS1NfUEVSX0NISUxEPTEwMCAgICAgICAtZSBHRU5FUkFURURfRklMRVNfRElSPS9hcHAvZ2VuZXJhdGVkICAgICAgIC1lIEdFTkVSQVRFRF9GSUxFU19VUkxfUFJFRklYPS9nZW5lcmF0ZWQtZmlsZXMgICAgICAgLWUgUFVCTElDX0FQSV9CQVNFX1VSTD1odHRwczovL2FwaS54aWFveGlhb2ppYW5qaS5jb20gICAgICAgLXYgIiRHRU5FUkFURURfRElSOi9hcHAvZ2VuZXJhdGVkIiAgICAgICAtLXJlc3RhcnQgdW5sZXNzLXN0b3BwZWQgICAgICAgLS1jcHVzIDIgICAgICAgLS1tZW1vcnkgMmcgICAgICAgLS1oZWFsdGgtY21kICJzaCAtYyAiZ3JlcCAtcSBjZWxlcnkgL3Byb2MvMS9jbWRsaW5lIHx8IGV4aXQgMSIiICAgICAgIC0taGVhbHRoLWludGVydmFsIDMwcyAgICAgICAtLWhlYWx0aC10aW1lb3V0IDEwcyAgICAgICAtLWhlYWx0aC1yZXRyaWVzIDMgICAgICAgLS1oZWFsdGgtc3RhcnQtcGVyaW9kIDMwcyAgICAgICAkTE9HX09QVFMgICAgICAgIiRPTERfV09SS0VSX0lNQUdFIiAmJiBlY2hvICIgIFdvcmtlciByb2xsYmFjayBzdGFydGVkIgogIGZpCgogIGlmIFsgLW4gIiRPTERfV0VCX0lNQUdFIiBdOyB0aGVuCiAgICBlY2hvICJSb2xsaW5nIGJhY2sgV2ViIHRvICRPTERfV0VCX0lNQUdFIC4uLiIKICAgIGRvY2tlciBydW4gLWQgICAgICAgLS1uYW1lIHhpYW94aWEtd2ViLXByb2R1Y3Rpb24gICAgICAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gICAgICAgLXAgMTI3LjAuMC4xOjMwMDI6ODAgICAgICAgLS1yZXN0YXJ0IHVubGVzcy1zdG9wcGVkICAgICAgIC0tY3B1cyAwLjUgICAgICAgLS1tZW1vcnkgNTEybSAgICAgICAkTEVHQUNZX1ZPTFVNRSAgICAgICAtLWhlYWx0aC1jbWQgIndnZXQgLS1zcGlkZXIgLXEgaHR0cDovLzEyNy4wLjAuMTo4MCIgICAgICAgLS1oZWFsdGgtaW50ZXJ2YWwgMzBzICAgICAgIC0taGVhbHRoLXRpbWVvdXQgNXMgICAgICAgLS1oZWFsdGgtcmV0cmllcyAzICAgICAgICRMT0dfT1BUUyAgICAgICAiJE9MRF9XRUJfSU1BR0UiICYmIGVjaG8gIiAgV2ViIHJvbGxiYWNrIHN0YXJ0ZWQiCiAgZmkKCiAgZWNobyAiIgogIGVjaG8gIuKaoO+4jyAgUm9sbGJhY2sgaW5pdGlhdGVkLiBTZXJ2aWNlIHNob3VsZCByZWNvdmVyIHdpdGhpbiA2MHMuIgogIGVjaG8gIiAgICBQbGVhc2UgaW52ZXN0aWdhdGUgdGhlIGRlcGxveW1lbnQgZmFpbHVyZSBmb3IgdmVyc2lvbiAkSU1BR0VfVEFHIgogIGV4aXQgMQp9CgojIC0tLS0g5YGc5q2i5pen5a655ZmoIC0tLS0KZWNobyAiU3RvcHBpbmcgb2xkIGNvbnRhaW5lcnMuLi4iCiMg5YWIIHN0b3DvvIjlj5HpgIEgU0lHVEVSTe+8jOS8mOmbhemAgOWHuu+8ie+8jOWGjSBybQpkb2NrZXIgc3RvcCAtdCAxMCB4aWFveGlhLWFwaS1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKZG9ja2VyIHN0b3AgLXQgNSB4aWFveGlhLXdvcmtlci1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKZG9ja2VyIHN0b3AgLXQgNSB4aWFveGlhLXdlYi1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKZG9ja2VyIHJtIC1mIHhpYW94aWEtYXBpLXByb2R1Y3Rpb24gMj4vZGV2L251bGwgfHwgdHJ1ZQpkb2NrZXIgcm0gLWYgeGlhb3hpYS13b3JrZXItcHJvZHVjdGlvbiAyPi9kZXYvbnVsbCB8fCB0cnVlCmRvY2tlciBybSAtZiB4aWFveGlhLXdlYi1wcm9kdWN0aW9uIDI+L2Rldi9udWxsIHx8IHRydWUKCiMgLS0tLSDml6Xlv5fphY3nva7vvIjmiYDmnInlrrnlmajlhbHnlKjvvIkgLS0tLQpMT0dfT1BUUz0iLS1sb2ctZHJpdmVyIGpzb24tZmlsZSAtLWxvZy1vcHQgbWF4LXNpemU9NTBtIC0tbG9nLW9wdCBtYXgtZmlsZT0zIgoKIyAtLS0tIOWQr+WKqCBBUEkgLS0tLQplY2hvICJTdGFydGluZyBBUEkgY29udGFpbmVyLi4uIgpkb2NrZXIgcnVuIC1kIFwKICAtLW5hbWUgeGlhb3hpYS1hcGktcHJvZHVjdGlvbiBcCiAgLS1lbnYtZmlsZSAiJEVOVl9GSUxFIiBcCiAgLS1uZXR3b3JrIHhpYW94aWEtbmV0LXByb2R1Y3Rpb24gXAogIC1wIDEyNy4wLjAuMTo4MDAxOjgwMDAgXAogIC1lIEFQUF9FTlY9cHJvZHVjdGlvbiBcCiAgLWUgQVBQX1ZFUlNJT049IiRJTUFHRV9UQUciIFwKICAtZSBHRU5FUkFURURfRklMRVNfRElSPS9hcHAvZ2VuZXJhdGVkIFwKICAtZSBHRU5FUkFURURfRklMRVNfVVJMX1BSRUZJWD0vZ2VuZXJhdGVkLWZpbGVzIFwKICAtZSBQVUJMSUNfQVBJX0JBU0VfVVJMPWh0dHBzOi8vYXBpLnhpYW94aWFqaWFuamkuY29tIFwKICAtdiAiJEdFTkVSQVRFRF9ESVI6L2FwcC9nZW5lcmF0ZWQiIFwKICAtLXJlc3RhcnQgdW5sZXNzLXN0b3BwZWQgXAogIC0tY3B1cyAyIFwKICAtLW1lbW9yeSAyZyBcCiAgLS1oZWFsdGgtY21kICJweXRob24gLWMgXCJpbXBvcnQgdXJsbGliLnJlcXVlc3Q7IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4oJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9oZWFsdGgnLCB0aW1lb3V0PTUpXCIiIFwKICAtLWhlYWx0aC1pbnRlcnZhbCAzMHMgXAogIC0taGVhbHRoLXRpbWVvdXQgMTBzIFwKICAtLWhlYWx0aC1yZXRyaWVzIDMgXAogIC0taGVhbHRoLXN0YXJ0LXBlcmlvZCA0MHMgXAogICRMT0dfT1BUUyBcCiAgIiRMT0NBTF9BUEkiCgojIC0tLS0g5ZCv5YqoIFdvcmtlciAtLS0tCmVjaG8gIlN0YXJ0aW5nIFdvcmtlciBjb250YWluZXIuLi4iCmRvY2tlciBydW4gLWQgXAogIC0tbmFtZSB4aWFveGlhLXdvcmtlci1wcm9kdWN0aW9uIFwKICAtLWVudi1maWxlICIkRU5WX0ZJTEUiIFwKICAtLW5ldHdvcmsgeGlhb3hpYS1uZXQtcHJvZHVjdGlvbiBcCiAgLWUgQVBQX0VOVj1wcm9kdWN0aW9uIFwKICAtZSBBUFBfVkVSU0lPTj0iJElNQUdFX1RBRyIgXAogIC1lIFdPUktFUl9DT05DVVJSRU5DWT0xIFwKICAtZSBXT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRD0xMDAgXAogIC1lIEdFTkVSQVRFRF9GSUxFU19ESVI9L2FwcC9nZW5lcmF0ZWQgXAogIC1lIEdFTkVSQVRFRF9GSUxFU19VUkxfUFJFRklYPS9nZW5lcmF0ZWQtZmlsZXMgXAogIC1lIFBVQkxJQ19BUElfQkFTRV9VUkw9aHR0cHM6Ly9hcGkueGlhb3hpYWppYW5qaS5jb20gXAogIC12ICIkR0VORVJBVEVEX0RJUjovYXBwL2dlbmVyYXRlZCIgXAogIC0tcmVzdGFydCB1bmxlc3Mtc3RvcHBlZCBcCiAgLS1jcHVzIDIgXAogIC0tbWVtb3J5IDJnIFwKICAtLWhlYWx0aC1jbWQgInNoIC1jIFwiZ3JlcCAtcSBjZWxlcnkgL3Byb2MvMS9jbWRsaW5lIHx8IGV4aXQgMVwiIiBcCiAgLS1oZWFsdGgtaW50ZXJ2YWwgMzBzIFwKICAtLWhlYWx0aC10aW1lb3V0IDEwcyBcCiAgLS1oZWFsdGgtcmV0cmllcyAzIFwKICAtLWhlYWx0aC1zdGFydC1wZXJpb2QgMzBzIFwKICAkTE9HX09QVFMgXAogICIkTE9DQUxfV09SS0VSIgoKIyAtLS0tIOWQr+WKqCBXZWIgLS0tLQojIExlZ2FjeSBhc3NldHMg5oyC6L295YiwIC91c3Ivc2hhcmUvbmdpbngvaHRtbC9hc3NldHMtbGVnYWN5L2Fzc2V0cy8KIyBuZ2lueCDphY3nva7kuK0gYXNzZXRzIGxvY2F0aW9uIOaciSBmYWxsYmFjayDpgLvovpEKTEVHQUNZX1ZPTFVNRT0iIgppZiBbIC1kICIkTEVHQUNZX0FTU0VUU19ESVIiIF0gJiYgWyAiJChscyAtQSAiJExFR0FDWV9BU1NFVFNfRElSIiAyPi9kZXYvbnVsbCkiIF07IHRoZW4KICBMRUdBQ1lfVk9MVU1FPSItdiAke0xFR0FDWV9BU1NFVFNfRElSfTovdXNyL3NoYXJlL25naW54L2h0bWwvYXNzZXRzLWxlZ2FjeS9hc3NldHM6cm8iCiAgZWNobyAiV2ViIGNvbnRhaW5lcjogbGVnYWN5IGFzc2V0cyBtb3VudGVkIChmYWxsYmFjaykiCmVsc2UKICBlY2hvICJXZWIgY29udGFpbmVyOiBubyBsZWdhY3kgYXNzZXRzIHRvIG1vdW50IgpmaQoKZWNobyAiU3RhcnRpbmcgV2ViIGNvbnRhaW5lci4uLiIKZG9ja2VyIHJ1biAtZCBcCiAgLS1uYW1lIHhpYW94aWEtd2ViLXByb2R1Y3Rpb24gXAogIC0tbmV0d29yayB4aWFveGlhLW5ldC1wcm9kdWN0aW9uIFwKICAtcCAxMjcuMC4wLjE6MzAwMjo4MCBcCiAgLS1yZXN0YXJ0IHVubGVzcy1zdG9wcGVkIFwKICAtLWNwdXMgMC41IFwKICAtLW1lbW9yeSA1MTJtIFwKICAkTEVHQUNZX1ZPTFVNRSBcCiAgLS1oZWFsdGgtY21kICJ3Z2V0IC0tc3BpZGVyIC1xIGh0dHA6Ly8xMjcuMC4wLjE6ODAiIFwKICAtLWhlYWx0aC1pbnRlcnZhbCAzMHMgXAogIC0taGVhbHRoLXRpbWVvdXQgNXMgXAogIC0taGVhbHRoLXJldHJpZXMgMyBcCiAgJExPR19PUFRTIFwKICAiJExPQ0FMX1dFQiIKCiMgLS0tLSDnrYnlvoUgQVBJIOWBpeW6tyAtLS0tCmVjaG8gIldhaXRpbmcgZm9yIEFQSSB0byBiZWNvbWUgaGVhbHRoeS4uLiIKaT0wCndoaWxlIFsgIiRpIiAtbHQgNDAgXTsgZG8KICBpZiBjdXJsIC1zZiAtLW1heC10aW1lIDUgaHR0cDovLzEyNy4wLjAuMTo4MDAxL2hlYWx0aCA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIkFQSSBpcyBoZWFsdGh5ISIKICAgIGJyZWFrCiAgZmkKICBpPSQoKGkgKyAxKSkKICBlY2hvICIgIFdhaXRpbmcuLi4gKCRpLzQwKSIKICBzbGVlcCAzCmRvbmUKCmlmIFsgIiRpIiAtZ2UgNDAgXTsgdGhlbgogIGVjaG8gIkVSUk9SOiBBUEkgZGlkIG5vdCBiZWNvbWUgaGVhbHRoeSB3aXRoaW4gMTIwcyIKICBkb2NrZXIgbG9ncyAtLXRhaWwgNTAgeGlhb3hpYS1hcGktcHJvZHVjdGlvbgogIHJvbGxiYWNrX3Byb2R1Y3Rpb24KZmkKCiMgLS0tLSDnrYnlvoUgV2ViIOWBpeW6tyAtLS0tCmVjaG8gIldhaXRpbmcgZm9yIFdlYiB0byBiZWNvbWUgaGVhbHRoeS4uLiIKaT0wCndoaWxlIFsgIiRpIiAtbHQgMTUgXTsgZG8KICBpZiBjdXJsIC1zZiAtLW1heC10aW1lIDUgaHR0cDovLzEyNy4wLjAuMTozMDAyLyA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIldlYiBpcyBoZWFsdGh5ISIKICAgIGJyZWFrCiAgZmkKICBpPSQoKGkgKyAxKSkKICBlY2hvICIgIFdhaXRpbmcuLi4gKCRpLzE1KSIKICBzbGVlcCAyCmRvbmUKCmlmIFsgIiRpIiAtZ2UgMTUgXTsgdGhlbgogIGVjaG8gIkVSUk9SOiBXZWIgZGlkIG5vdCBiZWNvbWUgaGVhbHRoeSB3aXRoaW4gMzBzIgogIGRvY2tlciBsb2dzIC0tdGFpbCAzMCB4aWFveGlhLXdlYi1wcm9kdWN0aW9uCiAgcm9sbGJhY2tfcHJvZHVjdGlvbgpmaQoKIyAtLS0tIOa4heeQhuaXp+mVnOWDjyAtLS0tCmVjaG8gIkNsZWFuaW5nIHVwIG9sZCBpbWFnZXMuLi4iCmRvY2tlciBpbWFnZSBwcnVuZSAtYWYgLS1maWx0ZXIgInVudGlsPTE2OGgiIDI+L2Rldi9udWxsIHx8IHRydWUKZG9ja2VyIGJ1aWxkZXIgcHJ1bmUgLWFmIC0tZmlsdGVyICJ1bnRpbD0xNjhoIiAyPi9kZXYvbnVsbCB8fCB0cnVlCgplY2hvICIiCmVjaG8gIj09PSBQcm9kdWN0aW9uIGRlcGxveW1lbnQgY29tcGxldGUgPT09IgplY2hvICJSb2xsYmFjay1yZWFkeTogb2xkIGltYWdlcyBwcmVzZXJ2ZWQgZm9yIHF1aWNrIHJlY292ZXJ5IgplY2hvICJBUEk6ICAgICAgaHR0cDovLzEyNy4wLjAuMTo4MDAxIgplY2hvICJXZWI6ICAgICAgaHR0cDovLzEyNy4wLjAuMTozMDAyIgplY2hvICJWZXJzaW9uOiAgJElNQUdFX1RBRyIKZG9ja2VyIHBzIC0tZm9ybWF0ICJ0YWJsZSB7ey5OYW1lc319XHR7ey5TdGF0dXN9fVx0e3suSW1hZ2V9fSIgfCBncmVwIHByb2R1Y3Rpb24K" echo "$DEPLOY_B64" | base64 -d | ssh -p 22222 -i "$key_path" "$production_user@$production_host" "IMAGE_TAG='${GITHUB_REF_NAME}' REGISTRY_TOKEN='${REGISTRY_TOKEN}' sh" -- 2.54.0 From 58107ca6ce68c24a00f3569dfba02c8ad9cfd8b9 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:56:01 +0800 Subject: [PATCH 11/19] =?UTF-8?q?ci(P2-3):=20=E6=94=BE=E5=AE=BD=20validate?= =?UTF-8?q?=20job=20timeout=20=E4=BB=8E10=E5=88=86=E9=92=9F=E5=88=B020?= =?UTF-8?q?=E5=88=86=E9=92=9F=EF=BC=8C=E9=81=BF=E5=85=8D=E5=81=B6=E5=8F=91?= =?UTF-8?q?=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index c071ed7bb..6ce794ac4 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -23,7 +23,7 @@ jobs: validate: name: Validate Code Quality And Tests runs-on: ubuntu-22.04 - timeout-minutes: 10 + timeout-minutes: 20 env: DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/xiaoxia_saas -- 2.54.0 From f2b3e16ca1c67030437a39c0c7880b56f9ec06a8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:56:14 +0800 Subject: [PATCH 12/19] =?UTF-8?q?ci(P2-4):=20=E5=A2=9E=E5=8A=A0=20concurre?= =?UTF-8?q?ncy=20=E6=8E=A7=E5=88=B6=EF=BC=8C=E5=90=8C=E4=B8=80=E5=88=86?= =?UTF-8?q?=E6=94=AF=E9=87=8D=E5=A4=8Dpush=E8=87=AA=E5=8A=A8=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E6=97=A7=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 6ce794ac4..987d91a3b 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -1,5 +1,9 @@ name: CI/CD Pipeline +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: push: branches: -- 2.54.0 From f58b4bd5d61642887100132dc934c240bb5bcbcb Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:56:44 +0800 Subject: [PATCH 13/19] =?UTF-8?q?ci(P2-6):=20=E6=89=80=E6=9C=89Playwright?= =?UTF-8?q?=20E2E=E6=B5=8B=E8=AF=95=E5=A2=9E=E5=8A=A0--retries=3D2?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=EF=BC=8C=E9=99=8D=E4=BD=8E?= =?UTF-8?q?=E5=81=B6=E5=8F=91=E5=A4=B1=E8=B4=A5=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 987d91a3b..35c0bfe58 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -614,7 +614,7 @@ jobs: -v "$PWD:/workspace" \ -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ - sh -lc "npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts" + sh -lc "npm ci && npx playwright test --reporter=line --retries=2 --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts" staging-api-tests: name: Staging API Integration Tests @@ -679,7 +679,7 @@ jobs: -v "$PWD:/workspace" \ -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ - sh -lc 'npm ci && npx playwright test --reporter=line e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts' + sh -lc 'npm ci && npx playwright test --reporter=line --retries=2 e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts' build-production-runtime-images: @@ -893,4 +893,4 @@ jobs: -v "$PWD:/workspace" \ -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ - sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts' + sh -lc 'npm ci && npx playwright test --reporter=line --retries=2 --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts' -- 2.54.0 From e52b7bcaac3d569f47378edc5a4cb06cc30d97a1 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:56:59 +0800 Subject: [PATCH 14/19] =?UTF-8?q?ci(P2-6):=20daily-check=E4=B8=AD=E6=89=80?= =?UTF-8?q?=E6=9C=89Playwright=20E2E=E6=B5=8B=E8=AF=95=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/daily-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/daily-check.yml b/.gitea/workflows/daily-check.yml index 2f147825f..0dfa8c6e2 100644 --- a/.gitea/workflows/daily-check.yml +++ b/.gitea/workflows/daily-check.yml @@ -213,7 +213,7 @@ jobs: -v "$PWD:/workspace" \ -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ - sh -lc "npm ci && npx playwright test --reporter=line e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts" 2>&1 | tee /tmp/staging-api-e2e.log + sh -lc "npm ci && npx playwright test --reporter=line --retries=2 e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts" 2>&1 | tee /tmp/staging-api-e2e.log EXIT_CODE=${PIPESTATUS[0]} END_TIME=$(date +%s) ELAPSED=$((END_TIME - START_TIME)) @@ -312,7 +312,7 @@ jobs: -v "$PWD:/workspace" \ -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ - sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts' 2>&1 | tee /tmp/staging-e2e.log + sh -lc 'npm ci && npx playwright test --reporter=line --retries=2 --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts' 2>&1 | tee /tmp/staging-e2e.log EXIT_CODE=${PIPESTATUS[0]} END_TIME=$(date +%s) ELAPSED=$((END_TIME - START_TIME)) -- 2.54.0 From e14e007537fd4fb4f9f401354a228074426525d8 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:57:33 +0800 Subject: [PATCH 15/19] =?UTF-8?q?ci(P2-2):=20validate=20job=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0pip=E4=BE=9D=E8=B5=96=E7=BC=93=E5=AD=98=EF=BC=8C?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=E9=87=8D=E5=A4=8D=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 35c0bfe58..2a150a1e6 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -88,6 +88,14 @@ jobs: python3 -m pip --version echo "CI environment is ready" + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies shell: sh run: | -- 2.54.0 From 48f5f8efa5ba7f6ad09c7cdca5117d40ffb10da0 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:58:09 +0800 Subject: [PATCH 16/19] =?UTF-8?q?ci(P2-5):=20=E6=B5=8B=E8=AF=95=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=97=B6=E4=B8=8A=E4=BC=A0=E7=BB=93=E6=9E=9C=E4=BA=A7?= =?UTF-8?q?=E7=89=A9=EF=BC=8Cpytest=E8=BE=93=E5=87=BA=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 2a150a1e6..ca6251575 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -176,7 +176,8 @@ jobs: USE_IN_MEMORY_DB: "true" run: | set -eu - PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -q + mkdir -p test-results + PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -v --junitxml=test-results/unit-tests.xml --tb=short - name: Start PostgreSQL for integration tests shell: sh @@ -221,7 +222,17 @@ jobs: run: | set -eu pip install -q pytest-rerunfailures - PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" + mkdir -p test-results + PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -v --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" --junitxml=test-results/integration-tests.xml --tb=short + + - name: Upload test results on failure + if: failure() + uses: actions/upload-artifact@v3 + with: + name: test-results-${{ github.run_id }} + path: | + test-results/ + *.log - name: Run API performance baseline tests shell: sh -- 2.54.0 From bf2649e445042e374ffeb2436e870b492d1ac38a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 13:58:58 +0800 Subject: [PATCH 17/19] =?UTF-8?q?ci(P2-7):=20=E5=A2=9E=E5=8A=A0Trivy?= =?UTF-8?q?=E9=95=9C=E5=83=8F=E5=AE=89=E5=85=A8=E6=89=AB=E6=8F=8F=EF=BC=88?= =?UTF-8?q?=E9=AB=98=E5=8D=B1=E6=BC=8F=E6=B4=9E=E6=A3=80=E6=B5=8B=EF=BC=8C?= =?UTF-8?q?=E4=BB=85=E5=91=8A=E8=AD=A6=E4=B8=8D=E9=98=BB=E6=96=AD=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci-cd.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index ca6251575..97ae9a987 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -765,6 +765,24 @@ jobs: chmod +x scripts/build_release_images.sh REGISTRY_TOKEN="${REGISTRY_TOKEN}" scripts/build_release_images.sh "${GITHUB_REF_NAME}" + - name: Trivy image security scan + continue-on-error: true + shell: sh + run: | + set +e + echo "=== Running Trivy security scan on production images ===" + IMAGE_TAG="${GITHUB_REF_NAME}" + REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas" + + # 扫描 API 镜像(只报告 CRITICAL 和 HIGH 级别漏洞) + for svc in api worker web; do + echo "" + echo "--- Scanning xiaoxia-saas-${svc}:${IMAGE_TAG} ---" + docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v trivy-cache:/root/.cache/ aquasec/trivy:latest image --severity CRITICAL,HIGH --ignore-unfixed --format table "xiaoxia-saas-${svc}:${IMAGE_TAG}" 2>&1 | tail -30 + done + echo "" + echo "=== Trivy scan complete (advisory only, does not block deploy) ===" + - name: Cleanup old Docker images if: always() shell: sh -- 2.54.0 From 8a9ee6ab11f211129c381e32df038fd60951aac2 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 14:00:22 +0800 Subject: [PATCH 18/19] =?UTF-8?q?ci(P2-8):=20=E4=BC=98=E5=8C=96api.Dockerf?= =?UTF-8?q?ile=E9=95=9C=E5=83=8F=E4=BD=93=E7=A7=AF=EF=BC=8C=E6=B8=85?= =?UTF-8?q?=E7=90=86Python=E5=AD=97=E8=8A=82=E7=A0=81=E5=92=8Capt=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/docker/api.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/docker/api.Dockerfile b/infra/docker/api.Dockerfile index 7c280c190..1ba93519e 100755 --- a/infra/docker/api.Dockerfile +++ b/infra/docker/api.Dockerfile @@ -39,6 +39,9 @@ COPY migrations/ /app/migrations/ COPY alembic/ /app/alembic/ COPY scripts/ /app/scripts/ +# 清理不需要的文件,减小镜像体积 +RUN find /app -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && find /opt/venv -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && find /opt/venv -name "*.pyc" -delete 2>/dev/null || true && find /opt/venv -name "*.pyo" -delete 2>/dev/null || true && rm -rf /opt/venv/share/doc /opt/venv/share/man 2>/dev/null || true && apt-get clean && rm -rf /var/lib/apt/lists/* + # 设置环境变量 ENV PATH="/opt/venv/bin:$PATH" ENV PYTHONPATH=/app -- 2.54.0 From bbfc45e5f73e384c7677ec3f899a27325c217303 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 9 Jul 2026 14:00:23 +0800 Subject: [PATCH 19/19] =?UTF-8?q?ci(P2-8):=20=E4=BC=98=E5=8C=96worker.Dock?= =?UTF-8?q?erfile=E9=95=9C=E5=83=8F=E4=BD=93=E7=A7=AF=EF=BC=8C=E6=B8=85?= =?UTF-8?q?=E7=90=86Python=E5=AD=97=E8=8A=82=E7=A0=81=E5=92=8Capt=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/docker/worker.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/docker/worker.Dockerfile b/infra/docker/worker.Dockerfile index 1822e2111..a2ebe8fd8 100755 --- a/infra/docker/worker.Dockerfile +++ b/infra/docker/worker.Dockerfile @@ -48,6 +48,9 @@ COPY packages/ /app/packages/ COPY alembic.ini /app/alembic.ini COPY migrations/ /app/migrations/ +# 清理不需要的文件,减小镜像体积 +RUN find /app -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && find /opt/venv -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && find /opt/venv -name "*.pyc" -delete 2>/dev/null || true && find /opt/venv -name "*.pyo" -delete 2>/dev/null || true && rm -rf /opt/venv/share/doc /opt/venv/share/man 2>/dev/null || true && apt-get clean && rm -rf /var/lib/apt/lists/* + # 设置 Python 路径 ENV PATH="/opt/venv/bin:$PATH" ENV PYTHONPATH=/app -- 2.54.0