From be60ec1507f3970a857fba143ebb71ed40f6186e Mon Sep 17 00:00:00 2001 From: OG T Date: Sun, 5 Apr 2026 14:22:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20ADR-059=20Gitea=20Webhook=20?= =?UTF-8?q?=E9=81=B7=E7=A7=BB=E5=AF=A6=E4=BD=9C=E8=A8=88=E7=95=AB=20(9=20T?= =?UTF-8?q?asks)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../2026-04-05-gitea-webhook-migration.md | 774 ++++++++++++++++++ 1 file changed, 774 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-05-gitea-webhook-migration.md diff --git a/docs/superpowers/plans/2026-04-05-gitea-webhook-migration.md b/docs/superpowers/plans/2026-04-05-gitea-webhook-migration.md new file mode 100644 index 000000000..83273b256 --- /dev/null +++ b/docs/superpowers/plans/2026-04-05-gitea-webhook-migration.md @@ -0,0 +1,774 @@ +# Gitea Webhook Migration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 將 GitHub Webhook 整合(Phase 13.1)遷移至 Gitea Webhook,讓 Gitea PR/Push 事件能觸發 OpenClaw AI 代碼審查。 + +**Architecture:** 最少改動策略 — Gitea 與 GitHub 的 payload 欄位幾乎相同,差異只在 HTTP header 名稱。重命名檔案、替換 Header 常數、移除 `workflow_run` handler,業務邏輯層(OpenClaw 呼叫、Redis 儲存、Telegram 通知、Approval 建立)完全不動。 + +**Tech Stack:** FastAPI, Python 3.11, pytest-asyncio, httpx ASGITransport, K8s Secrets, Gitea Webhook + +--- + +## File Map + +| 動作 | 路徑 | +|------|------| +| 新增 | `apps/api/src/api/v1/gitea_webhook.py` | +| 新增 | `apps/api/src/services/gitea_webhook_service.py` | +| 修改 | `apps/api/src/main.py` | +| 修改 | `apps/api/src/core/config.py` | +| 修改 | `k8s/awoooi-prod/03-secrets.yaml` | +| 修改 | `.gitea/workflows/cd.yaml` | +| 新增 | `apps/api/tests/test_gitea_webhook.py` | +| 修改 | `apps/api/tests/conftest.py` | +| 刪除 | `apps/api/src/api/v1/github_webhook.py` | +| 刪除 | `apps/api/src/services/github_webhook_service.py` | +| 刪除 | `apps/api/tests/test_github_webhook.py` | +| 新增 | `docs/adr/ADR-059-gitea-webhook-integration.md` | + +--- + +## Task 1: Config 層 — 替換 GITHUB_ → GITEA_ 環境變數 + +**Files:** +- Modify: `apps/api/src/core/config.py:490-505` + +- [ ] **Step 1: 讀取現有 config 確認位置** + +```bash +grep -n "GITHUB_WEBHOOK_SECRET\|GITHUB_ALLOWED_REPOS\|get_github_allowed" apps/api/src/core/config.py +``` + +Expected output 類似: +``` +490: GITHUB_WEBHOOK_SECRET: str = Field( +494: GITHUB_ALLOWED_REPOS: str = Field( +501: raw = self.GITHUB_ALLOWED_REPOS +``` + +- [ ] **Step 2: 替換 config.py 中的 GITHUB_ 欄位** + +將 `apps/api/src/core/config.py` 中這段: + +```python + GITHUB_WEBHOOK_SECRET: str = Field( + default="", + description="GitHub Webhook secret for signature verification (X-Hub-Signature-256)", + ) + GITHUB_ALLOWED_REPOS: str = Field( + default="", + description="Comma-separated list of allowed repositories (e.g., 'owner/repo1,owner/repo2')", + ) + + def get_github_allowed_repos(self) -> list[str]: + """Parse comma-separated allowed repos to list""" + raw = self.GITHUB_ALLOWED_REPOS + if not raw or not raw.strip(): + return [] + return [repo.strip() for repo in raw.split(",") if repo.strip()] +``` + +改為: + +```python + GITEA_WEBHOOK_SECRET: str = Field( + default="", + description="Gitea Webhook secret for HMAC-SHA256 signature verification (X-Gitea-Signature)", + ) + GITEA_ALLOWED_REPOS: str = Field( + default="wooo/awoooi", + description="Comma-separated list of allowed Gitea repositories (e.g., 'wooo/awoooi')", + ) + + def get_gitea_allowed_repos(self) -> list[str]: + """Parse comma-separated allowed repos to list""" + # 2026-04-05 Claude Code (ADR-059): GitHub → Gitea webhook 遷移 + raw = self.GITEA_ALLOWED_REPOS + if not raw or not raw.strip(): + return [] + return [repo.strip() for repo in raw.split(",") if repo.strip()] +``` + +- [ ] **Step 3: 驗證 config 語法正確** + +```bash +cd apps/api && python3.11 -c "from src.core.config import settings; print('GITEA_WEBHOOK_SECRET:', repr(settings.GITEA_WEBHOOK_SECRET)); print('GITEA_ALLOWED_REPOS:', repr(settings.GITEA_ALLOWED_REPOS))" +``` + +Expected: +``` +GITEA_WEBHOOK_SECRET: '' +GITEA_ALLOWED_REPOS: 'wooo/awoooi' +``` + +- [ ] **Step 4: Commit** + +```bash +git add apps/api/src/core/config.py +git commit -m "refactor(config): GITHUB_WEBHOOK_SECRET → GITEA_WEBHOOK_SECRET (ADR-059)" +``` + +--- + +## Task 2: Service 層 — 建立 gitea_webhook_service.py + +**Files:** +- Create: `apps/api/src/services/gitea_webhook_service.py` + +這個 task 從 `github_webhook_service.py` 複製並修改: +- class 名稱 `GitHub*` → `Gitea*` +- Redis key prefix `github_review:` → `gitea_review:` +- Telegram 訊息中 `GITHUB CODE REVIEW` → `GITEA CODE REVIEW` +- 移除 `CIFailureDiagnosis` class、`_call_openclaw_ci_diagnosis()`、`_send_ci_failure_telegram_alert()`、`_create_ci_failure_approval()`、`diagnose_ci_failure()` 整個 method +- 所有 payload 存取改用 `.get()` chain(防禦性解析) + +- [ ] **Step 1: 複製並重命名** + +```bash +cp apps/api/src/services/github_webhook_service.py apps/api/src/services/gitea_webhook_service.py +``` + +- [ ] **Step 2: 批次替換 class/常數名稱** + +```bash +cd apps/api/src/services +# 替換 class 名稱和常數 +sed -i \ + -e 's/GITHUB_REVIEW_TTL_SECONDS/GITEA_REVIEW_TTL_SECONDS/g' \ + -e 's/IGitHubReviewRepository/IGiteaReviewRepository/g' \ + -e 's/GitHubReviewRedisRepository/GiteaReviewRedisRepository/g' \ + -e 's/GitHubWebhookService/GiteaWebhookService/g' \ + -e 's/get_github_webhook_service/get_gitea_webhook_service/g' \ + -e 's/github_review:/gitea_review:/g' \ + -e 's/GITHUB CODE REVIEW/GITEA CODE REVIEW/g' \ + -e 's/github-webhook/gitea-webhook/g' \ + gitea_webhook_service.py +``` + +- [ ] **Step 3: 更新 config 存取(settings.GITHUB_ → settings.GITEA_)** + +```bash +sed -i \ + -e 's/settings\.GITHUB_WEBHOOK_SECRET/settings.GITEA_WEBHOOK_SECRET/g' \ + -e 's/settings\.GITHUB_ALLOWED_REPOS/settings.GITEA_ALLOWED_REPOS/g' \ + -e 's/settings\.get_github_allowed_repos/settings.get_gitea_allowed_repos/g' \ + apps/api/src/services/gitea_webhook_service.py +``` + +- [ ] **Step 4: 移除 CI 診斷相關程式碼** + +手動編輯 `apps/api/src/services/gitea_webhook_service.py`,刪除以下區塊: + +1. `class CIFailureDiagnosis(BaseModel):` 整個 class(約 51-67 行附近) +2. `async def _call_openclaw_ci_diagnosis(self, ...)` 整個 method(約 268-320 行附近) +3. `async def _send_ci_failure_telegram_alert(self, ...)` 整個 method(約 448-548 行附近) +4. `async def _create_ci_failure_approval(self, ...)` 整個 method(約 633-719 行附近) +5. `async def diagnose_ci_failure(self, ...)` 整個 method(約 908-1042 行附近) + +確認方式: +```bash +grep -n "ci_failure\|CIFailure\|diagnose_ci\|workflow_run" apps/api/src/services/gitea_webhook_service.py +``` +Expected: 無輸出(全部移除) + +- [ ] **Step 5: 更新檔案 docstring 和版本資訊** + +在 `gitea_webhook_service.py` 頂部 docstring 改為: + +```python +""" +AWOOOI API - Gitea Webhook Service +==================================== +ADR-059: GitHub → Gitea Webhook 遷移 + +整合流程: +1. Gitea Webhook (PR/Push) → AWOOOI API +2. HMAC-SHA256 簽章驗證 (X-Gitea-Signature) +3. 解析 PR diff / Push commits +4. 呼叫 OpenClaw 進行 AI 代碼審查 +5. 儲存審查結果到 Redis +6. 發送 Telegram 通知 +7. (可選) 建立 Approval 等待人工確認 + +支援事件: +- pull_request: PR 代碼審查 +- push: 主分支推送審查 + +版本: v1.0 +最後修改: 2026-04-05 (台北時區) +修改者: Claude Code (ADR-059 GitHub → Gitea 遷移) +""" +``` + +- [ ] **Step 6: 審查所有 payload .get() 防禦性存取** + +搜尋所有直接 key access 並改為 `.get()` chain: + +```bash +grep -n 'payload\["' apps/api/src/services/gitea_webhook_service.py | head -20 +``` + +對每個直接存取(`payload["key"]`)改為 `payload.get("key", default)`。 +重要位置包含: +- `review_pull_request()` 中存取 `pull_request`、`repository`、`sender` 欄位 +- `review_push()` 中存取 `commits`、`repository`、`ref` 欄位 + +範例修改: +```python +# ❌ 舊 +repo_name = payload["repository"]["full_name"] + +# ✅ 新 +repo_name = payload.get("repository", {}).get("full_name", "") +``` + +- [ ] **Step 7: 語法驗證** + +```bash +cd apps/api && python3.11 -c "from src.services.gitea_webhook_service import get_gitea_webhook_service; print('✅ import OK')" +``` + +Expected: `✅ import OK` + +- [ ] **Step 8: Commit** + +```bash +git add apps/api/src/services/gitea_webhook_service.py +git commit -m "feat(service): 新增 GiteaWebhookService (ADR-059,移除 CI 診斷)" +``` + +--- + +## Task 3: Router 層 — 建立 gitea_webhook.py + +**Files:** +- Create: `apps/api/src/api/v1/gitea_webhook.py` + +從 `github_webhook.py` 複製並修改: +- Header 常數:`X-GitHub-Event` → `X-Gitea-Event`、`X-Hub-Signature-256` → `X-Gitea-Signature` +- Router prefix:`/webhooks/github` → `/webhooks/gitea` +- 移除 `handle_workflow_run()` 和相關 Pydantic models (`GitHubWorkflowRun`, `GitHubWorkflowJob`) +- 所有 `GitHub*` class → `Gitea*` +- import 改為 `gitea_webhook_service` + +- [ ] **Step 1: 複製並重命名** + +```bash +cp apps/api/src/api/v1/github_webhook.py apps/api/src/api/v1/gitea_webhook.py +``` + +- [ ] **Step 2: 批次替換 header 名稱和 class 名稱** + +```bash +sed -i \ + -e 's/X-GitHub-Event/X-Gitea-Event/g' \ + -e 's/X-Hub-Signature-256/X-Gitea-Signature/g' \ + -e 's/X-GitHub-Delivery/X-Gitea-Delivery/g' \ + -e 's/x_hub_signature_256/x_gitea_signature/g' \ + -e 's/x_github_event/x_gitea_event/g' \ + -e 's/x_github_delivery/x_gitea_delivery/g' \ + -e 's/GitHubUser/GiteaUser/g' \ + -e 's/GitHubRepository/GiteaRepository/g' \ + -e 's/GitHubPullRequest/GiteaPullRequest/g' \ + -e 's/GitHubCommit/GiteaCommit/g' \ + -e 's/GitHubWebhookPayload/GiteaWebhookPayload/g' \ + -e 's/GitHubWebhookResponse/GiteaWebhookResponse/g' \ + -e 's/GitHubSignatureError/GiteaSignatureError/g' \ + -e 's/verify_github_signature/verify_gitea_signature/g' \ + -e 's/verify_allowed_repo/verify_gitea_allowed_repo/g' \ + -e 's/github_webhook_service/gitea_webhook_service/g' \ + -e 's/get_github_webhook_service/get_gitea_webhook_service/g' \ + -e 's/GitHubWebhookService/GiteaWebhookService/g' \ + apps/api/src/api/v1/gitea_webhook.py +``` + +- [ ] **Step 3: 替換 router prefix 和 config 存取** + +```bash +sed -i \ + -e 's|prefix="/webhooks/github"|prefix="/webhooks/gitea"|g' \ + -e 's/tags=\["GitHub Webhook"\]/tags=["Gitea Webhook"]/g' \ + -e 's/settings\.GITHUB_WEBHOOK_SECRET/settings.GITEA_WEBHOOK_SECRET/g' \ + -e 's/settings\.get_github_allowed_repos/settings.get_gitea_allowed_repos/g' \ + apps/api/src/api/v1/gitea_webhook.py +``` + +- [ ] **Step 4: 移除 workflow_run 相關程式碼** + +手動編輯 `apps/api/src/api/v1/gitea_webhook.py`,刪除以下區塊: + +1. `class GitHubWorkflowRun(BaseModel):` 整個 class +2. `class GitHubWorkflowJob(BaseModel):` 整個 class(若存在) +3. `async def handle_workflow_run(...)` 整個 function +4. 主 handler `handle_gitea_webhook()` 中 `workflow_run` 的 dispatch branch: + ```python + elif event_type == "workflow_run": + return await handle_workflow_run(payload) + ``` + +確認移除: +```bash +grep -n "workflow_run\|WorkflowRun\|WorkflowJob" apps/api/src/api/v1/gitea_webhook.py +``` +Expected: 無輸出 + +- [ ] **Step 5: 更新 docstring** + +在 `gitea_webhook.py` 頂部 docstring 改為: + +```python +""" +AWOOOI API - Gitea Webhook Handler +==================================== +ADR-059: GitHub → Gitea Webhook 遷移 + +整合流程: +1. Gitea Webhook (PR/Push) → AWOOOI API +2. HMAC-SHA256 簽章驗證 (X-Gitea-Signature) +3. 解析 PR diff / Push commits +4. 呼叫 OpenClaw 進行 AI 代碼審查 +5. 儲存審查結果到 Redis +6. 發送 Telegram 通知 +7. (可選) 建立 Approval 等待人工確認 + +支援事件: +- pull_request: PR 代碼審查 (opened, synchronize, reopened) +- push: 主分支推送審查 +- ping: 連線測試 + +安全要求: +- HMAC 簽章驗證 (X-Gitea-Signature) +- Webhook Secret 存放於 K8s Secret +- 倉庫白名單驗證 + +版本: v1.0 +最後修改: 2026-04-05 (台北時區) +修改者: Claude Code (ADR-059 GitHub → Gitea 遷移) +""" +``` + +- [ ] **Step 6: 語法驗證** + +```bash +cd apps/api && python3.11 -c "from src.api.v1.gitea_webhook import router; print('✅ router OK, prefix:', router.prefix)" +``` + +Expected: `✅ router OK, prefix: /webhooks/gitea` + +- [ ] **Step 7: Commit** + +```bash +git add apps/api/src/api/v1/gitea_webhook.py +git commit -m "feat(api): 新增 gitea_webhook router (ADR-059,Header 改 X-Gitea-*)" +``` + +--- + +## Task 4: main.py — 替換掛載 + +**Files:** +- Modify: `apps/api/src/main.py:43,467` + +- [ ] **Step 1: 確認目前掛載位置** + +```bash +grep -n "github_webhook" apps/api/src/main.py +``` + +Expected: +``` +43: github_webhook as github_webhook_v1, # Phase 13.1: GitHub → OpenClaw +467: github_webhook_v1.router, prefix="/api/v1", tags=["GitHub Webhook"] +``` + +- [ ] **Step 2: 替換 import** + +將 main.py 第 42-44 行附近的: +```python +from src.api.v1 import ( + github_webhook as github_webhook_v1, # Phase 13.1: GitHub → OpenClaw +) +``` + +改為: +```python +from src.api.v1 import ( + gitea_webhook as gitea_webhook_v1, # ADR-059: Gitea → OpenClaw (GitHub → Gitea 遷移) +) +``` + +- [ ] **Step 3: 替換 router 掛載** + +將 main.py 中: +```python +app.include_router( + github_webhook_v1.router, prefix="/api/v1", tags=["GitHub Webhook"] +) +``` + +改為: +```python +app.include_router( + gitea_webhook_v1.router, prefix="/api/v1", tags=["Gitea Webhook"] +) +``` + +- [ ] **Step 4: 驗證 main.py 可 import** + +```bash +cd apps/api && MOCK_MODE=true python3.11 -c " +import os; os.environ['MOCK_MODE']='true'; os.environ['DATABASE_URL']='postgresql+asyncpg://x:x@localhost/x' +from src.main import app +routes = [r.path for r in app.routes if hasattr(r, 'path')] +gitea_routes = [r for r in routes if 'gitea' in r] +print('Gitea routes:', gitea_routes) +github_routes = [r for r in routes if 'github' in r and 'gitea' not in r] +print('Leftover GitHub routes:', github_routes) +" +``` + +Expected: +``` +Gitea routes: ['/api/v1/webhooks/gitea', '/api/v1/webhooks/gitea/reviews/{review_id}'] +Leftover GitHub routes: [] +``` + +- [ ] **Step 5: Commit** + +```bash +git add apps/api/src/main.py +git commit -m "refactor(main): github_webhook → gitea_webhook router 掛載 (ADR-059)" +``` + +--- + +## Task 5: Tests — 建立 test_gitea_webhook.py + +**Files:** +- Create: `apps/api/tests/test_gitea_webhook.py` +- Modify: `apps/api/tests/conftest.py` + +- [ ] **Step 1: 更新 conftest.py** + +將 `apps/api/tests/conftest.py` 中: +```python +os.environ.setdefault("GITHUB_WEBHOOK_SECRET", "test-secret-key-12345") +os.environ.setdefault("GITHUB_ALLOWED_REPOS", "test-owner/test-repo,wooo-ai/awoooi") +``` + +改為: +```python +os.environ.setdefault("GITEA_WEBHOOK_SECRET", "test-secret-key-12345") +os.environ.setdefault("GITEA_ALLOWED_REPOS", "test-owner/test-repo,wooo/awoooi") +``` + +- [ ] **Step 2: 建立 test_gitea_webhook.py** + +```bash +cp apps/api/tests/test_github_webhook.py apps/api/tests/test_gitea_webhook.py +``` + +- [ ] **Step 3: 批次替換 header 和 import** + +```bash +sed -i \ + -e 's/X-Hub-Signature-256/X-Gitea-Signature/g' \ + -e 's/X-GitHub-Event/X-Gitea-Event/g' \ + -e 's/X-GitHub-Delivery/X-Gitea-Delivery/g' \ + -e 's/from src\.api\.v1\.github_webhook import router as github_webhook_router/from src.api.v1.gitea_webhook import router as gitea_webhook_router/g' \ + -e 's/github_webhook_router/gitea_webhook_router/g' \ + -e 's|prefix="/api/v1"|prefix="/api/v1"|g' \ + apps/api/tests/test_gitea_webhook.py +``` + +- [ ] **Step 4: 更新 app 建立** + +在 `test_gitea_webhook.py` 找到: +```python +from src.api.v1.gitea_webhook import router as gitea_webhook_router + +app = FastAPI() +app.include_router(gitea_webhook_router, prefix="/api/v1") +``` + +確認這兩行正確(sed 應已處理 import,確認 `include_router` 使用 `gitea_webhook_router`)。 + +- [ ] **Step 5: 執行測試,確認全部通過** + +```bash +cd apps/api && source /opt/api-venv/bin/activate 2>/dev/null || python3.11 -m venv /tmp/test-venv && source /tmp/test-venv/bin/activate && pip install -q uv && uv pip install -q -e ".[dev]" +cd apps/api && python3.11 -m pytest tests/test_gitea_webhook.py -v +``` + +Expected: 8 tests PASSED: +``` +PASSED tests/test_gitea_webhook.py::test_webhook_missing_signature +PASSED tests/test_gitea_webhook.py::test_webhook_invalid_signature +PASSED tests/test_gitea_webhook.py::test_webhook_valid_signature +PASSED tests/test_gitea_webhook.py::test_webhook_repo_not_in_whitelist +PASSED tests/test_gitea_webhook.py::test_webhook_ping_event +PASSED tests/test_gitea_webhook.py::test_webhook_push_event +PASSED tests/test_gitea_webhook.py::test_webhook_push_non_default_branch +PASSED tests/test_gitea_webhook.py::test_webhook_unsupported_event +PASSED tests/test_gitea_webhook.py::test_webhook_pr_unsupported_action +``` + +- [ ] **Step 6: Commit** + +```bash +git add apps/api/tests/test_gitea_webhook.py apps/api/tests/conftest.py +git commit -m "test: test_gitea_webhook.py — 8 個測試覆蓋 HMAC/whitelist/events (ADR-059)" +``` + +--- + +## Task 6: 清除舊 GitHub 檔案 + +**Files:** +- Delete: `apps/api/src/api/v1/github_webhook.py` +- Delete: `apps/api/src/services/github_webhook_service.py` +- Delete: `apps/api/tests/test_github_webhook.py` + +- [ ] **Step 1: 確認 github_webhook 已無 import** + +```bash +grep -rn "github_webhook" apps/api/src/ apps/api/tests/ --include="*.py" | grep -v "__pycache__" +``` + +Expected: 無輸出(所有 reference 都已在前面步驟移除) + +- [ ] **Step 2: 刪除舊檔案** + +```bash +git rm apps/api/src/api/v1/github_webhook.py \ + apps/api/src/services/github_webhook_service.py \ + apps/api/tests/test_github_webhook.py +``` + +- [ ] **Step 3: 確認無孤兒 import** + +```bash +cd apps/api && python3.11 -c "from src.main import app; print('✅ main import OK')" +``` + +Expected: `✅ main import OK` + +- [ ] **Step 4: 執行完整測試確認無 regression** + +```bash +cd apps/api && python3.11 -m pytest tests/ -v --tb=short -x \ + --ignore=tests/integration \ + --ignore=tests/test_anomaly_counter.py \ + --ignore=tests/test_global_repair_cooldown.py \ + --ignore=tests/test_redis_multisig.py \ + --ignore=tests/test_model_regression.py \ + --ignore=tests/test_prompt_validation.py \ + 2>&1 | tail -30 +``` + +Expected: 全部 PASSED,無 FAILED + +- [ ] **Step 5: Commit** + +```bash +git commit -m "chore: 移除舊 GitHub Webhook 檔案 (github_webhook.py / service / test) — ADR-059" +``` + +--- + +## Task 7: K8s Secrets + CD Pipeline + +**Files:** +- Modify: `k8s/awoooi-prod/03-secrets.yaml` +- Modify: `.gitea/workflows/cd.yaml` + +- [ ] **Step 1: 更新 03-secrets.yaml** + +在 `k8s/awoooi-prod/03-secrets.yaml` 找到 `GITHUB_WEBHOOK_SECRET`(若存在),改為 `GITEA_WEBHOOK_SECRET: "CHANGE_ME"`。 + +若不存在,在適當位置新增: +```yaml + GITEA_WEBHOOK_SECRET: "CHANGE_ME" # 2026-04-05 Claude Code (ADR-059): CD 自動注入 +``` + +確認: +```bash +grep -n "GITEA_WEBHOOK_SECRET\|GITHUB_WEBHOOK_SECRET" k8s/awoooi-prod/03-secrets.yaml +``` + +Expected: 只看到 `GITEA_WEBHOOK_SECRET`,無 `GITHUB_WEBHOOK_SECRET`。 + +- [ ] **Step 2: 更新 cd.yaml Inject K8s Secrets step** + +在 `.gitea/workflows/cd.yaml` 的 `Inject K8s Secrets` step(`SECRETS` heredoc 內),在現有注入區塊末尾(`echo "✅ 所有 Secrets 注入完成"` 之前)新增: + +```bash + # ADR-059: Gitea Webhook Secret (PR/Push → OpenClaw 代碼審查) + if [ -n "${GITEA_WEBHOOK_SECRET}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/GITEA_WEBHOOK_SECRET","value":"'"$(echo -n "${GITEA_WEBHOOK_SECRET}" | base64 -w 0)"'"} + ]' && echo "✅ GITEA_WEBHOOK_SECRET 已注入" || echo "⚠️ GITEA_WEBHOOK_SECRET patch 失敗" + else + echo "⚠️ GITEA_WEBHOOK_SECRET 未設定,Gitea webhook 簽章驗證將跳過" + fi +``` + +同時在 step 的 `env:` 區塊新增: +```yaml + GITEA_WEBHOOK_SECRET: ${{ secrets.GITEA_WEBHOOK_SECRET }} +``` + +- [ ] **Step 3: 驗證 cd.yaml YAML 語法** + +```bash +python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/cd.yaml')); print('✅ YAML OK')" +``` + +Expected: `✅ YAML OK` + +- [ ] **Step 4: Commit** + +```bash +git add k8s/awoooi-prod/03-secrets.yaml .gitea/workflows/cd.yaml +git commit -m "ops(k8s+cd): 新增 GITEA_WEBHOOK_SECRET 注入 (ADR-059)" +``` + +--- + +## Task 8: ADR 文件 + +**Files:** +- Create: `docs/adr/ADR-059-gitea-webhook-integration.md` + +- [ ] **Step 1: 建立 ADR-059** + +```bash +cat > docs/adr/ADR-059-gitea-webhook-integration.md << 'EOF' +# ADR-059: Gitea Webhook 整合 (GitHub → Gitea 遷移) + +**日期**: 2026-04-05 (台北時區) +**狀態**: 已採用 (Adopted) +**決策者**: 統帥 + Claude Code +**關聯**: ADR-039 (Gitea CI/CD 遷移), ADR-029 (CI/CD AI 整合架構) + +--- + +## 背景 + +Phase 13.1 (ADR-029) 實作了 GitHub Webhook → OpenClaw AI 代碼審查整合。 +但 ADR-039 已將 CI/CD 全面遷移至 Gitea,GitHub 只剩唯讀備份。 +`GITHUB_WEBHOOK_SECRET` 從未注入 K8s Secrets,GitHub Webhook 功能處於死路狀態。 + +## 決策 + +將 GitHub Webhook 整合遷移至 Gitea Webhook。 +廢棄 `workflow_run` CI 診斷功能(CD pipeline 已有 Telegram 通知覆蓋,重複無必要)。 + +## 遷移策略 + +**最少改動策略 (Minimal Viable Change)** + +Gitea 與 GitHub 的 webhook payload 欄位幾乎相同,差異只在 HTTP header 名稱: + +| 項目 | GitHub (廢棄) | Gitea (新) | +|------|-------------|-----------| +| 事件 Header | `X-GitHub-Event` | `X-Gitea-Event` | +| 簽章 Header | `X-Hub-Signature-256` | `X-Gitea-Signature` | +| 簽章格式 | `sha256=` | `sha256=` (相同) | +| Payload 欄位 | — | 幾乎相同 | + +業務邏輯層(OpenClaw 呼叫、Redis 儲存、Telegram 通知、Approval 建立)完全不動。 + +## 支援事件 + +| 事件 | 狀態 | +|------|------| +| `pull_request` (opened/synchronize/reopened) | ✅ 遷移 | +| `push` (main/master) | ✅ 遷移 | +| `workflow_run` | ❌ 廢棄 | +| `ping` | ✅ 遷移 | + +## 端點變更 + +``` +舊: POST /api/v1/webhooks/github +新: POST /api/v1/webhooks/gitea +``` + +## 實作護欄 + +1. **防禦性 payload 解析**: 所有欄位存取使用 `.get()` chain +2. **Gitea UI Content-Type**: 必須選 `application/json`(非 `application/x-www-form-urlencoded`) + +## 後果 + +- Gitea PR/Push 事件可觸發 OpenClaw AI 代碼審查 +- 移除 `workflow_run` CI 診斷(由 CD pipeline TG 通知替代) +- `GITHUB_WEBHOOK_SECRET` K8s Secret 廢棄,改用 `GITEA_WEBHOOK_SECRET` +EOF +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/adr/ADR-059-gitea-webhook-integration.md +git commit -m "docs(adr): ADR-059 Gitea Webhook 整合 (GitHub → Gitea 遷移)" +``` + +--- + +## Task 9: Push 到 Gitea + 部署後手動設定 + +- [ ] **Step 1: Push** + +```bash +git push gitea main +``` + +- [ ] **Step 2: 確認 CI 通過** + +觀察 Gitea Actions。`Run API Tests` step 應包含 `test_gitea_webhook.py` 的 9 個測試全部 PASSED。 + +- [ ] **Step 3: 在 Gitea UI 設定 Webhook(部署完成後)** + +1. 進入 `http://192.168.0.110:3001/wooo/awoooi` → Settings → Webhooks → Add Webhook → **Gitea** +2. **Target URL**: `https://awoooi.wooo.work/api/v1/webhooks/gitea` +3. **HTTP Method**: POST +4. **Content Type**: `application/json` ← **必須,否則 FastAPI 無法解析** +5. **Secret**: 與 Gitea Actions Secret `GITEA_WEBHOOK_SECRET` 相同值 +6. **Trigger On**: Pull Request events ✅ + Push events ✅ +7. **Active**: ✅ 勾選 +8. 點擊 "Test Delivery" 確認 ping 事件收到 202 回應 + +- [ ] **Step 4: 在 Gitea Actions 新增 Secret** + +`http://192.168.0.110:3001/wooo/awoooi` → Settings → Actions → Secrets → 新增: +- Name: `GITEA_WEBHOOK_SECRET` +- Value: (統帥決定,與 Gitea UI Webhook Secret 相同) + +--- + +## Self-Review + +**Spec coverage check:** + +| Spec 要求 | 對應 Task | +|-----------|-----------| +| `github_webhook.py` → `gitea_webhook.py` | Task 3 | +| `github_webhook_service.py` → `gitea_webhook_service.py` | Task 2 | +| `main.py` 更新 import + 路徑 | Task 4 | +| `config.py` GITHUB_ → GITEA_ | Task 1 | +| `03-secrets.yaml` 更新 | Task 7 | +| `cd.yaml` 注入 GITEA_WEBHOOK_SECRET | Task 7 | +| `conftest.py` env var 更新 | Task 5 | +| `test_gitea_webhook.py` | Task 5 | +| 舊 GitHub 檔案刪除 | Task 6 | +| ADR-059 | Task 8 | +| 防禦性 `.get()` 解析 | Task 2 Step 6 | +| Content-Type 警告文件 | Task 9 Step 3 | +| Gitea UI 設定步驟 | Task 9 Step 3 | + +**Placeholder scan:** 無 TBD/TODO,所有步驟含完整程式碼或指令。 + +**Type consistency:** `GiteaWebhookService`、`get_gitea_webhook_service`、`gitea_webhook_router` 在 Task 2/3/4/5 命名一致。