Files
awoooi/docs/superpowers/specs/2026-04-05-gitea-webhook-migration-design.md
OG T 5cd67d372f docs(spec): ADR-059 Gitea Webhook 遷移設計規格
從 GitHub Webhook (Phase 13.1) 遷移至 Gitea Webhook
最少改動策略:Header 常數替換,業務邏輯層不動
廢棄 workflow_run CI 診斷(CD pipeline 已有 TG 通知覆蓋)
整合首席架構師護欄:防禦性 payload 解析 + Content-Type 設定

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:17:13 +08:00

186 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gitea Webhook 整合設計規格
**日期**: 2026-04-05 (台北時區)
**作者**: Claude Code + 首席架構師 Review
**狀態**: 已批准,待實作
**ADR**: ADR-059 (待建立)
**關聯**: ADR-039 (Gitea CI/CD 遷移), ADR-029 (CI/CD AI 整合架構)
---
## 背景
Phase 13.1 實作了 GitHub Webhook → OpenClaw AI 代碼審查整合,但:
1. CI/CD 已於 ADR-039 全面遷移至 GiteaGitHub 只剩唯讀備份
2. `GITHUB_WEBHOOK_SECRET` 從未注入 K8s Secrets功能實際上處於死路狀態
3. 代碼審查的觸發源應改為 Gitea實際開發倉庫
**決策:遷移至 Gitea Webhook廢棄 GitHub Webhook 整合。**
---
## 設計原則
**最少改動策略 (Minimal Viable Change)**
Gitea 與 GitHub 的 webhook payload 欄位幾乎相同,差異只在 HTTP header 名稱。
因此策略是:重命名 + Header 常數替換業務邏輯層OpenClaw 呼叫、Redis 儲存、Telegram 通知、Approval 建立)完全不動。
---
## Header 差異對照
| 項目 | GitHub (廢棄) | Gitea (新) |
|------|-------------|-----------|
| 事件類型 Header | `X-GitHub-Event` | `X-Gitea-Event` |
| 簽章 Header | `X-Hub-Signature-256` | `X-Gitea-Signature` |
| 投遞 ID Header | `X-GitHub-Delivery` | `X-Gitea-Delivery` |
| 簽章格式 | `sha256=<hex>` | `sha256=<hex>`**相同** |
| Payload 欄位 | — | — ← **幾乎相同** |
> Gitea 會同時送出 `X-GitHub-Event` 相容 header但我們應讀取原生的 `X-Gitea-Event`
> 語意清晰,未來不依賴相容層。
---
## 遷移範圍
### 支援事件
| 事件 | GitHub (舊) | Gitea (新) | 說明 |
|------|------------|-----------|------|
| `pull_request` | ✅ | ✅ 遷移 | PR 代碼審查 |
| `push` | ✅ | ✅ 遷移 | 主分支推送審查 |
| `workflow_run` | ✅ | ❌ 廢棄 | CD pipeline 已有 TG 通知,重複覆蓋無必要 |
| `ping` | ✅ | ✅ 遷移 | 連線測試 |
### 檔案變更清單
| 檔案 | 動作 | 說明 |
|------|------|------|
| `src/api/v1/github_webhook.py` | 重命名 → `gitea_webhook.py` | Header 常數改 Gitea移除 `workflow_run` handler |
| `src/services/github_webhook_service.py` | 重命名 → `gitea_webhook_service.py` | 移除 `diagnose_ci_failure()`class 改名 |
| `src/main.py` | 修改 | 更新 import掛載路徑改 `/webhooks/gitea` |
| `src/core/config.py` | 修改 | `GITHUB_WEBHOOK_SECRET``GITEA_WEBHOOK_SECRET``GITHUB_ALLOWED_REPOS``GITEA_ALLOWED_REPOS` |
| `k8s/awoooi-prod/03-secrets.yaml` | 修改 | 新增 `GITEA_WEBHOOK_SECRET`,移除 `GITHUB_WEBHOOK_SECRET` |
| `tests/conftest.py` | 修改 | 更新 env var 名稱 |
| `tests/test_github_webhook.py` | 重命名 → `test_gitea_webhook.py` | Header 常數改 Gitea |
| `docs/adr/ADR-059-gitea-webhook-integration.md` | 新增 | 決策記錄 |
---
## API 端點
```
舊: POST /api/v1/webhooks/github
新: POST /api/v1/webhooks/gitea
舊: GET /api/v1/webhooks/github/reviews/{review_id}
新: GET /api/v1/webhooks/gitea/reviews/{review_id}
```
---
## Config 變更
```python
# 舊 (廢棄)
GITHUB_WEBHOOK_SECRET: str = Field(default="")
GITHUB_ALLOWED_REPOS: str = Field(default="")
# 新
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')",
)
```
---
## 防禦性 Payload 解析(首席架構師護欄 #1
Gitea 部分欄位為 nullable 或可能缺失。所有欄位存取使用 `.get()` chain禁止直接 key access
```python
# ❌ 禁止
repo_name = payload["repository"]["full_name"]
# ✅ 正確
repo_name = payload.get("repository", {}).get("full_name", "")
pr_number = payload.get("pull_request", {}).get("number", 0)
head_sha = payload.get("pull_request", {}).get("head", {}).get("sha", "")
```
現有 `github_webhook_service.py` 中已部分採用此模式,遷移時需全面審查確認。
---
## Gitea UI 設定步驟(首席架構師護欄 #2
部署完成後在 Gitea 設定 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_WEBHOOK_SECRET` K8s Secret 值相同
6. **Trigger On**: Pull Request events + Push events
7. **Active**: 勾選
> ⚠️ Content-Type 若選 `application/x-www-form-urlencoded`FastAPI 的 `Request.json()`
> 將無法解析,導致所有 webhook 請求失敗。
---
## 簽章驗證邏輯
驗證邏輯與 GitHub 完全相同,只需更換 header 名稱:
```python
# 舊 header
x_hub_signature_256: str | None = Header(default=None, alias="X-Hub-Signature-256")
# 新 header
x_gitea_signature: str | None = Header(default=None, alias="X-Gitea-Signature")
```
HMAC-SHA256 計算方式不變:`sha256=<hmac_sha256(secret, body).hexdigest()>`
---
## K8s Secrets 注入
`k8s/awoooi-prod/03-secrets.yaml` 新增:
```yaml
GITEA_WEBHOOK_SECRET: "CHANGE_ME" # CD 自動注入
```
`cd.yaml``Inject K8s Secrets` step 需新增注入:
```bash
kubectl patch secret awoooi-secrets \
--patch "{\"stringData\": {\"GITEA_WEBHOOK_SECRET\": \"${{ secrets.GITEA_WEBHOOK_SECRET }}\"}}"
```
Gitea Actions secrets 需新增 `GITEA_WEBHOOK_SECRET`
---
## 測試策略
`test_gitea_webhook.py` 複用現有 8 個測試案例,更新:
1. Header 名稱:`X-Hub-Signature-256``X-Gitea-Signature``X-GitHub-Event``X-Gitea-Event`
2. Router import`from src.api.v1.gitea_webhook import router as gitea_webhook_router`
3. `conftest.py``GITHUB_WEBHOOK_SECRET``GITEA_WEBHOOK_SECRET``GITHUB_ALLOWED_REPOS``GITEA_ALLOWED_REPOS`
---
## 不在此 Scope
- Gitea 端實際 webhook secret 的生成與管理(統帥決定)
- `workflow_run` 等值替代方案(未來 Phase 規劃)
- GitHub mirror repo 的 webhook 設定清除(可手動處理)