# 22 — CD 生产环境落地方案

> 状态：草案，待评审
> 作者：庞统
> 日期：2026-06-11

## 背景

CD 管道已在测试仓库 `sanguo/mojiplus-v2` 验证通过（run#282 全链路 success）。
现需将 CD 落地到生产仓库 `sanguo/sanguo_moziplus_v2`。

### 当前状态

| 组件 | 状态 |
|---|---|
| `deploy.sh` | ✅ 完整（rsync + build + pm2 restart + health check + deploy history） |
| `deploy.yml` | ⚠️ deploy job 已调用 deploy.sh，但缺少成功通知 |
| CI 失败通知 | ✅ ci.yml → PR comment → webhook → Mail |
| Deploy 失败通知 | ✅ deploy.yml → Issue → webhook → Mail |
| Deploy 成功通知 | ❌ 缺失 |

## 改动方案

### 改动 1：deploy.yml 增加 deploy 成功通知

在 deploy job 最后增加一个 step，deploy 成功后：
1. 从 Gitea API 查询触发 commit 关联的 merged PR
2. 获取 PR 作者
3. 通过 Mail API 发送成功通知给 PR 作者 + pangtong-fujunshi
4. 如果是 direct push（非 PR merge），只通知 jiangwei-infra + pangtong-fujunshi

**文件**：`.gitea/workflows/deploy.yml`

**改动范围**：deploy job 内新增 1 个 step（约 30 行 shell）

**关键逻辑**：
```bash
# 查询关联 PR
PR_INFO=$(curl -sf \
  -H "Authorization: token $GITEA_TOKEN" \
  "$API_URL/repos/$REPO/pulls?state=closed&limit=5" | \
  python3 -c "
import json,sys
for pr in json.load(sys.stdin):
    if pr.get('merge_commit_sha','') == '$COMMIT_SHA':
        print(pr['user']['login'])
        break
" 2>/dev/null || echo "")

# 发 Mail
curl -s -X POST http://localhost:8083/api/mail \
  -H "Content-Type: application/json" \
  -d "{\"from\":\"daemon\",\"to\":\"$PR_AUTHOR\",\"title\":\"...\",\"text\":\"...\",\"type\":\"inform\"}"
```

**约束**：
- 使用 `if: always()` + shell 判断 `needs.deploy.result == "success"`，确保只在成功时执行
- GITEA_TOKEN 通过 secrets 注入
- Mail API 调用超时 5 秒，失败不影响部署结果

### 不改的文件

| 文件 | 原因 |
|---|---|
| `src/api/toolchain_routes.py` | 不新增 webhook 事件，deploy 成功通知在 yml 内闭环 |
| `src/daemon/toolchain_handler.py` | 不涉及 |
| `templates/toolchain/*.md` | 不新增模板，通知内容直接在 shell 中构建 |
| `scripts/deploy.sh` | 已完整，不需改动 |

## 影响范围

- **风险**：低。只在 deploy job 末尾追加通知 step，不修改已有的 deploy/notify 逻辑
- **回退**：删除新增 step 即可
- **测试**：push main 后观察 deploy workflow 执行结果

## E2E 验证计划

1. 在生产仓库创建测试分支，push → PR → merge → 触发 deploy
2. 验证 deploy 成功后 Mail 通知到达 PR 作者
3. 验证部署文件同步到 `~/.sanguo_projects/sanguo_moziplus_v2/`
4. 验证 health check 通过
