From 927d7072cebda4c62872622505e57a4c3fe4c3fa Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 4 May 2026 14:21:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(p34):=20cd.yaml=20=E5=8A=A0=20SPA=20Shadow?= =?UTF-8?q?=20=E5=81=B5=E6=B8=AC=20=E2=80=94=20=E9=98=B2=20nginx=20fallbac?= =?UTF-8?q?k=20=E5=81=BD=E7=B6=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 過去 5 個 deploy(run 273-277)全 success 但 prod 上 Flask 從未接到請求 — nginx 對所有路徑 fallback 到 SPA index.html (7480 byte / etag e167a58a...) — 原健康檢查只看 HTTP 200,被 SPA shell 200 騙過。 新增第 3 階段檢查(接在原 HTTP 200 retry + 三容器驗證之後): 驗 /health response 三條 fingerprint 任一不符 SPA shell 即 Flask 真接到: (a) Content-Length != 7480 (b) etag != e167a58a1baf907f55a2925a2e8665d1 (c) x-process-time header 存在(Flask middleware 加的,nginx static 不會帶) 三條全失敗 = SPA 攔截 → 推 Telegram + exit 1(CD 紅)。 TELEGRAM secrets 未設時跳過告警不阻 deploy。 修了過去那種「我推 commit、CD 全綠、實際 prod 0 影響」的盲點。 --- .gitea/workflows/cd.yaml | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index ecd356d..11e18fc 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -291,8 +291,12 @@ jobs: echo "ℹ️ Monitoring 設定未變更,略過重新載入" fi - # ── 健康檢查(H3: HTTP + 三容器狀態雙重驗證) ───────────────────────── + # ── 健康檢查(H3: HTTP + 三容器 + Phase 34 SPA Shadow 偵測) ────────── - name: 健康檢查 + env: + COMMIT_SHA: ${{ steps.commit.outputs.short_sha }} + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | echo "⏳ 等待服務啟動(30s)..." sleep 30 @@ -320,6 +324,40 @@ jobs: echo "✅ 三容器均正常運行($RUNNING/3)"; \ fi' + # ── Phase 34 SPA Shadow 偵測 — 防 nginx fallback 偽綠 ─────────────── + # 過去 5 個 deploy(run 273-277)全 success 但 prod 上 Flask 從未接到請求, + # 因為 nginx 對 /admin/* 等路徑全 fallback 到 SPA index.html(7480 byte)。 + # 三條 fingerprint 任一不符 SPA = Flask 真接到: + # (a) Content-Length != 7480 + # (b) etag != e167a58a1baf907f55a2925a2e8665d1 + # (c) x-process-time header 存在(Flask middleware) + echo "🔍 SPA Shadow 偵測(驗 Flask 真接到請求)..." + SPA_ETAG='e167a58a1baf907f55a2925a2e8665d1' + SPA_LEN='7480' + # 用 /health(純 Flask,不會被 SPA 路徑攔)做基準探針 + HDR=$(curl -sS -D - -o /dev/null --max-time 10 https://mo.wooo.work/health 2>/dev/null || echo "") + ETAG=$(echo "$HDR" | grep -i '^etag:' | tr -d '"\r' | awk '{print $2}' | tr 'A-Z' 'a-z') + CLEN=$(echo "$HDR" | grep -i '^content-length:' | awk '{print $2}' | tr -d '\r') + XPT=$(echo "$HDR" | grep -i '^x-process-time:' | awk '{print $2}' | tr -d '\r') + FLASK_OK=0 + [ -n "$XPT" ] && [ "$XPT" != "0" ] && FLASK_OK=1 + [ -n "$ETAG" ] && [ "$ETAG" != "$SPA_ETAG" ] && FLASK_OK=1 + [ -n "$CLEN" ] && [ "$CLEN" != "$SPA_LEN" ] && FLASK_OK=1 + if [ "$FLASK_OK" != "1" ]; then + echo "❌ SPA Shadow 偵測:/health 看似 200 但 nginx fallback 攔截" + echo " etag=$ETAG (SPA=$SPA_ETAG)" + echo " content-length=$CLEN (SPA=$SPA_LEN)" + echo " x-process-time=$XPT (Flask 應 > 0)" + # Telegram 告警(如 secrets 已設) + if [ -n "${TELEGRAM_BOT_TOKEN}" ] && [ -n "${TELEGRAM_CHAT_ID}" ]; then + MSG="🚨 EwoooC SPA Shadow 偵測警告%0A├ commit ${COMMIT_SHA}%0A├ /health 被 nginx SPA fallback 攔截%0A└ 立即查 nginx upstream / Flask container" + curl -sS -m 5 -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" -d "text=${MSG}" -d "parse_mode=HTML" >/dev/null || true + fi + exit 1 + fi + echo "✅ SPA Shadow OK — Flask 真接到請求(etag=$ETAG xpt=$XPT clen=$CLEN)" + # ── 觸發 Post-Deploy Code Review ───────────────────────────────────── - name: 觸發 AI Code Review if: success()