Files
cc-web/docs/gitea-workflow/DEPLOYMENT.md

148 lines
9.0 KiB
Markdown
Raw Permalink 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 Agent 部署与运维设计
> 本文是 [PRD.md](./PRD.md) 与 [ARCHITECTURE.md](./ARCHITECTURE.md) 的运行手册。
> 它定义部署、凭据、恢复和故障处置边界Gitea 仍是唯一主交互入口cc-web 页面
> 只承担配置、镜像和运维控制。
## 1. 运行基线
MVP 采用单 Gitea 实例(`instanceId=default`)和专用 `ccweb-bot`。同仓库永远
串行,跨仓库允许并行,不设置全局并发上限。生产环境至少需要:
| 项目 | 基线 |
|---|---|
| Node/Bun | 使用项目锁定的运行时和已验收的单文件发布包;不要混用系统 Node |
| 进程 | 一个 cc-web worker外部 Codex App Server 按会话复用 |
| 持久目录 | `WORKFLOW_DATA_ROOT`(任务/审计/租约)与 `WORKSPACE_ROOT`Git 工作区)分离 |
| 网络 | Gitea Webhook 入站、Gitea REST/MCP 出站、Codex App Server 本地 stdio/JSON-RPC |
| 时间 | 主机启用 NTP持久化时间全部使用 UTC ISO-8601 |
| 访问 | 反向代理终止 TLSWebhook 路径只允许 POST 和受限请求体大小 |
建议为 worker、工作区和日志使用独立系统用户。`WORKFLOW_DATA_ROOT` 仅 worker
可读写,`WORKSPACE_ROOT` 不允许 Web 静态服务暴露,备份目录使用不同权限主体。
## 2. 配置与凭据
配置按“非敏感文件 → Secret 管理 → 线程级注入”分层。示例(值均为占位符):
```ini
CC_WEB_GITEA_HOST=https://gitea.example
CC_WEB_GITEA_INSTANCE_ID=default
CC_WEB_GITEA_BOT_LOGIN=ccweb-bot
GITEA_WEBHOOK_SECRET_FILE=/etc/ccweb/secrets/gitea-webhook
GITEA_BOT_TOKEN_FILE=/etc/ccweb/secrets/gitea-bot-token
CC_WEB_GITEA_WORKFLOW_STATE=/var/lib/ccweb/gitea-workflow/state.json
CC_WEB_GITEA_WORKSPACE_ROOT=/var/lib/ccweb/workspaces
```
cc-web 管理页对应接口为:
- `GET /api/gitea-workflow/config`返回地址、Bot 用户名、工作区和凭据是否已配置;不会返回 Secret/Token。
- `PUT /api/gitea-workflow/config`:保存上述配置,敏感字段写入受保护的运行配置;保存后立即更新当前进程的 Webhook、Git 和 gitea-mcp 线程配置,不要求重启 cc-web。
- `GET /api/gitea-workflow/overview``/tasks``/audit`:状态镜像。
- `POST /api/gitea-workflow/control/pause|resume`、仓库 `enable|disable`、任务 `cancel|abort`:需要 cc-web 登录 Bearer Token服务端自动记录操作来源和请求幂等 ID页面不要求用户填写审计字段。
- 内部部署可以不配置 Webhook Secret配置后入口启用 HMAC-SHA256 验签。Bot Token
只在 gitea-mcp/REST 出站边界使用,不能把 Bot Token 当作 Webhook Secret。
- Secret 文件权限为 `0600`、属主为 worker日志、审计、评论正文和 Git remote
URL 禁止写入 Secret。审计只记录 `secretRef/tokenRef` 和不可逆指纹。
- `thread/start.config.mcp_servers.gitea.env` 使用线程级 secret 引用或运行时
注入值;不能把某个来源会话 ID、Bot Token 或 Gitea Secret 放在长驻
app-server 的进程级全局环境中。
- `gitea-mcp` 随 cc-web 源码/发布目录放在 `bin/gitea-mcp`,不需要为每个服务单独
安装。只有替换版本时才使用 `CC_WEB_GITEA_MCP_COMMAND` 指向本机绝对路径;可选的
`CC_WEB_GITEA_MCP_STARTUP_TIMEOUT_MS` 控制 initialize 预检超时(默认 8000ms
上限 30000ms。cc-web 发布流程不从网络自动下载或安装外部二进制。
- 轮换 Secret 时先配置新值并验证签名,再撤销旧值;未配置 Secret 的内部入口应
由反向代理/网络白名单限制来源。Bot Token 更新后立即作用于后续 MCP 线程。
## 3. Webhook 与反向代理
Gitea 只向反向代理暴露的 HTTPS 路径发送 Webhook。代理应
1. 严格保留原始请求 body 和 `X-Gitea-Signature``X-Gitea-Delivery`
`X-Gitea-Event` 头,不做 JSON 重排后再交给验签层。
2. 限制方法为 POST、请求体大小建议 1 MiB和连接超时超限返回 413。
3. 只将来自 Gitea 网段的请求转发到 worker不要在代理层伪造签名或 delivery。
4. 配置了 Secret 时worker 验签失败返回 401解析失败返回 400首次入队返回
202重复 delivery/忽略事件返回 200。未配置 Secret 的内部入口仍需由代理
限制来源;持久化不可用时返回 503。
5. 代理访问日志脱敏 `Authorization`、签名值和正文;保留 request ID 以便与
`deliveryKey` 关联。
验签必须针对 raw body 计算 HMAC-SHA256并使用常量时间比较。解析后的 mention
和仓库字段不能替代 raw body 验签;缺少 delivery header 时只能使用可靠 payload
ID否则拒绝入队。
## 4. 启动、健康检查与发布
启动顺序固定为:
1. 检查配置文件、Secret 文件权限、数据目录可写性和工作区根目录是否在允许
根下;禁止自动 `reset --hard``clean`
2. 打开持久 Store执行租约过期扫描和恢复扫描`queued` 重新入队,
`running/preparing/aborting` 按架构规则转为带 `interrupted_by_restart`
`retry_wait`,终态不重跑。
3. 启动 Scheduler 和受控的 Codex App Server完成 `initialize` 后发送
`initialized`,再 best-effort 探测 goals 与 collaboration mode。
4. 仅在健康检查通过后把 Webhook 代理切入当前 worker。
健康检查至少包含Store 读写探针、租约扫描完成、Gitea `/api/v1/version`
等价只读探针、Codex App Server initialize smoke以及线程级 gitea-mcp
stdio `initialize` 预检。健康检查不得创建评论、修改仓库或打印凭据。
发布采用“先旁路验证、再切流”:在 staging 用回归 fixtures 和真实 Gitea 测试
仓库验证 HMAC、入队、MCP 配置与回帖确认;生产切换前确认旧 worker 已停止领取
新任务且租约已释放。回滚只切换到上一份已验收发布包和数据快照,不删除当前
工作区或审计。
## 5. 日常运维控制
| 操作 | 影响 | 约束 |
|---|---|---|
| 全局 pause | 停止领取新任务,已运行任务继续 | 页面按钮直接执行,服务端记录操作 |
| resume | 恢复调度 | 先检查工作区和 Gitea 连通性 |
| repo disable | 拒绝该仓库新触发 | 不强杀运行中任务,保留历史 |
| cancel queued | 取消未启动任务 | 幂等;不得取消已领取任务 |
| abort running | 向 Codex App 请求取消 | 等待确认或超时后释放租约并审计 |
| replay delivery | 仅用于核对/恢复 | 先查 `deliveryKey`,禁止绕过去重直接执行 |
所有控制操作必须通过受保护的运维 API服务端生成操作记录和幂等 ID。API 不接受
任意 Git 命令、模型切换或 MCP 工具参数。
## 6. 监控与告警
结构化日志只输出关联 ID`deliveryKey、taskId、repoKey、sessionKey、threadId、
turnId、leaseId、errorCode`。建议监控Webhook 401/400/503 比例、队列年龄、锁等待、
运行任务数量、`retry_wait` 数量、`blocked_workspace` 数量、MCP 握手失败、回帖确认
缺失和 REST 兜底次数。
以下情况应告警并暂停自动扩容:连续 HMAC 失败、同一 delivery payload hash
变化、租约频繁过期、回帖兜底连续失败、工作区出现未知 owner 或路径越界、
Bot Token/Secret 可能泄露。告警正文不得包含 Secret、完整评论正文或源码。
## 7. 备份、恢复与数据保留
- 备份 Store 的任务、会话、turn、评论索引、控制面和 append-only 审计;
`webhook_deliveries` 至少保留覆盖 Gitea 重试窗口的记录。
- 工作区是可重建缓存,备份前先记录 `repoKey、HEAD、dirty 状态`;不要把
未提交用户修改静默覆盖或当成可恢复快照。
- 恢复顺序:停止领取 → 恢复 Store → 校验版本/唯一键 → 执行租约恢复扫描 →
只读检查工作区 → 启动 Scheduler → 逐步 resume。恢复后优先核对
`deliveryKey → taskId → turnId → commentId` 链路,避免重复回帖。
- 清理策略必须由管理员显式启用并保留审计;不得因磁盘告警直接删除运行中
任务、未确认评论或工作区。
## 8. 故障处置速查
| 症状 | 先查 | 安全动作 |
|---|---|---|
| Webhook 全部 401 | Secret 引用、raw body、代理头 | 暂停切流,验证单个 fixture不打印签名 |
| 任务堆积 | pause、锁、并发槽位、租约 | 不手工改状态;修复后让恢复扫描接管 |
| 工作区 blocked | `git status`、owner、锁文件 | 保留现场,管理员审查后再解除;禁止 reset/clean |
| App/MCP 不可达 | initialize、stdio stderr、hostRef、`gitea-mcp` 可执行文件 | 命令缺失、进程启动失败、握手失败或超时均立即进入 `failed` 并发送一条 `giteabot:` 失败回执;修复后重新触发 |
| MCP 成功但无评论 | resource/task/turn 标记、Gitea 查询 | 只允许一次 reply_retry随后 REST 兜底 |
| 重启后重复执行 | stateVersion、leaseId、终态记录 | 先 pause修复 Store/租约,再恢复;禁止批量重放 |
每次事故结束后导出关联审计、错误码、重试次数和最终评论 ID形成可复盘记录。