diff --git a/backend/requirements.txt b/backend/requirements.txt index 39c2879..d380190 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -72,6 +72,8 @@ python-dotenv==1.0.1 pyotp==2.9.0 # bcrypt: 密码哈希库(用于本地密码认证) bcrypt==4.1.2 +# passlib: 密码哈希兼容库(bcrypt 前端封装) +passlib[bcrypt]==1.7.4 # qrcode: 二维码生成(用于 OTP 绑定) qrcode[pil]==7.4.2 # pillow: 图片处理(qrcode[pil] 依赖) diff --git a/deploy-server/nginx/nginx.conf b/deploy-server/nginx/nginx.conf index ea2d593..379be9c 100644 --- a/deploy-server/nginx/nginx.conf +++ b/deploy-server/nginx/nginx.conf @@ -165,6 +165,7 @@ http { # WebSocket — /ws/(坐席端实时通信) # ------------------------------------------------------------------ location /ws/ { + access_log off; # P0-#4: 关闭 WS 路径日志,避免 token 泄露 proxy_pass http://backend_api; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; diff --git a/docs/ADRs/ADR-001-Gitea自托管-Funnel暴露.md b/docs/ADRs/ADR-001-Gitea自托管-Funnel暴露.md new file mode 100644 index 0000000..2d8d034 --- /dev/null +++ b/docs/ADRs/ADR-001-Gitea自托管-Funnel暴露.md @@ -0,0 +1,61 @@ +# ADR-001: Gitea 自托管 + Tailscale Funnel 暴露 + +**状态**: ✅ 已采纳 +**日期**: 2026-06-14 +**决策者**: 宋献 + Claude 评审 +**关联**: [[Gitea部署指南]] / [[风险跟踪表]] 第十二节 + +--- + +## 1. 背景 + +项目主仓 `D:\资料\03-项目开发\wecom_it_smart_desk\` 需要: +- 跨设备协作(simon's 电脑 + workbuddy 沙箱) +- 推送评审 + 分支保护 +- 异地可访问(workbuddy 沙箱无 Tailscale 内网) + +## 2. 评估方案 + +| 方案 | 优势 | 劣势 | 结论 | +|---|---|---|---| +| **A. GitHub 私有仓** | 零运维 + 全球 CDN + 完善 Actions | 代码在境外(企业合规风险)+ 付费 | ❌ 否决 | +| **B. GitLab.com 私有仓** | 免费私有 + 完善 CI | 代码在境外 + workbuddy 沙箱访问延迟 | ❌ 否决 | +| **C. Gitea 自托管(NAS)+ Tailscale Funnel** | 数据本地 + workbuddy 可访问 + 免费 | NAS 单点故障 + Funnel 稳定性依赖 Tailscale | ✅ **采纳** | +| **D. Gitea 自托管 + 公网 IP 暴露** | 不依赖 Tailscale | 需配 SSL + DDOS 风险 + 国内带宽限制 | ❌ 否决 | + +## 3. 决策 + +**采纳 C 方案**: Gitea 套件(DS923+ NAS)+ Tailscale Funnel 暴露公网。 + +## 4. 关键参数 + +| 项 | 值 | 备注 | +|---|---|---| +| Gitea 版本 | 1.22+ | 套件中心固定 | +| 端口 | 8418 (HTTP) | 避开被占端口 | +| 数据库 | SQLite3 | 单机够用,简化部署 | +| Tailscale 私网 | `tail58d872.ts.net` | DSM 已配 | +| Funnel 域名 | `https://ds923plus.tail58d872.ts.net` | 沙箱访问 | +| 备份 | `scripts/backup-gitea.sh` cron 3 点 | 见 [[Gitea部署指南]] §6 | +| 异地备份 | OSS / COS 推 | M-1 风险项待解决 | + +## 5. 风险与缓解 + +| 风险 | 等级 | 缓解 | +|---|---|---| +| NAS 硬盘故障 | 🟠 高 | 异地 OSS 备份(待配) | +| Tailscale Funnel 稳定性 | 🟡 中 | Funnel 故障时降级 LAN(`http://100.85.152.112:8418`) | +| 卸载误操作数据丢失 | 🟡 中 | 备份脚本 + 卸载前 checklist | +| token 泄露 | 🟠 高 | token 不入文件,走 wincred | + +## 6. 决策影响 + +- ✅ 团队协作无需 VPN(workbuddy 沙箱直连 Funnel) +- ✅ 推送评审 + 分支保护(PR + 1 reviewer) +- ⚠️ NAS 单点是隐患,需异地备份 +- ⚠️ 卸载/迁移需严格按 [[Gitea部署指南]] §8 走 + +## 7. 后续评审 + +- 3 个月后(2026-09-14)评审:Funnel 稳定性 + 备份完整度 +- 6 个月后(2026-12-14)评审:是否切到企业 GitLab(如果合规要求) diff --git a/docs/ADRs/ADR-002-WS-Token-Subprotocol鉴权.md b/docs/ADRs/ADR-002-WS-Token-Subprotocol鉴权.md new file mode 100644 index 0000000..b65fc2a --- /dev/null +++ b/docs/ADRs/ADR-002-WS-Token-Subprotocol鉴权.md @@ -0,0 +1,80 @@ +# ADR-002: WebSocket Token 鉴权(走 Sec-WebSocket-Protocol) + +**状态**: ✅ 已采纳 +**日期**: 2026-06-14 +**决策者**: 宋献 + Claude 评审 +**关联**: [[风险跟踪表]] 第十节 / 评审报告 `workbuddy-2026-06-14-P0安全.md` + +--- + +## 1. 背景 + +WebSocket 鉴权原方案:`ws://server/ws/?token=` —— **token 在 URL 里**: +- ❌ 被 nginx access_log 记录 +- ❌ 被 CDN / 反代记录 +- ❌ 被浏览器历史记录 + +**P0 漏洞**(H-11 风险项),已修复。 + +## 2. 评估方案 + +| 方案 | 浏览器支持 | token 泄露 | 实施难度 | 结论 | +|---|---|---|---|---| +| **A. Authorization: Bearer header** | ❌ 浏览器 WS API 不支持自定义 header | ✅ 不泄 | 中 | ❌ 否决(浏览器限制) | +| **B. Sec-WebSocket-Protocol: bearer.** | ✅ 现代浏览器都支持 | ✅ 不在 URL | 低 | ✅ **采纳** | +| **C. 第一条消息传 token** | ✅ 全支持 | ⚠️ 需先开 WS 接受任意连接(无法鉴权) | 低 | ❌ 否决 | +| **D. Cookie 自动带** | ✅ 全支持 | ⚠️ CSRF 风险 | 中 | ❌ 否决 | + +## 3. 决策 + +**采纳 B 方案**: `Sec-WebSocket-Protocol: bearer.` + +服务端协商 subprotocol,客户端用第二个 subprotocol 传 token(浏览器 API `new WebSocket(url, [subprotocols])`)。 + +## 4. 实现 + +### 4.1 前端 + +```ts +// frontend-agent/src/composables/useWebSocket.ts +const ws = new WebSocket(wsUrl, [`bearer.${agentStore.token}`]) +``` + +### 4.2 后端 + +```python +# backend/app/api/ws.py +subprotocol = request.headers.get("sec-websocket-protocol", "") +if subprotocol.startswith("bearer."): + token = subprotocol[7:] +else: + # 降级:Authorization header + auth = request.headers.get("Authorization", "") + if auth.startswith("Bearer "): + token = auth[7:] + else: + # 降级:query param(已废,只用于兼容旧前端) + token = request.query_params.get("token", "") +``` + +## 5. 降级路径 + +| 优先级 | 来源 | 用途 | +|---|---|---| +| 1 | Sec-WebSocket-Protocol | 标准(主) | +| 2 | Authorization: Bearer | Postman / 测试工具 | +| 3 | query `?token=` | 已废(留兼容) | + +## 6. 风险与缓解 + +| 风险 | 缓解 | +|---|---| +| 浏览器 API 不支持 subprotocol | 现代浏览器(2020+)都支持,无问题 | +| 旧客户端不更新 | query param 降级仍可用,但提示更新 | +| nginx 仍记录 subprotocol | `location /ws/ { access_log off; }` 配合 | + +## 7. 决策影响 + +- ✅ WS 鉴权修复,token 不再泄 +- ✅ nginx access_log 关闭,旧 token 不留痕 +- ⚠️ 旧客户端需更新(发版通知) diff --git a/docs/ADRs/ADR-003-nginx-access_log关闭.md b/docs/ADRs/ADR-003-nginx-access_log关闭.md new file mode 100644 index 0000000..adce573 --- /dev/null +++ b/docs/ADRs/ADR-003-nginx-access_log关闭.md @@ -0,0 +1,106 @@ +# ADR-003: nginx 敏感路径 access_log 关闭 + +**状态**: ✅ 已采纳 +**日期**: 2026-06-14 +**决策者**: 宋献 + Claude 评审 +**关联**: [[风险跟踪表]] 第十节 / 评审报告 `workbuddy-2026-06-14-P0安全.md` + +--- + +## 1. 背景 + +nginx `access_log` 默认记录所有请求,含敏感信息: +- `Authorization: Bearer ` +- `?token=` +- `Cookie: session=` + +敏感路径必须关闭 access_log,避免 token 永久落盘。 + +## 2. 决策 + +**敏感路径一律 `access_log off`**,具体见下表。 + +## 3. 关闭清单 + +| 路径 | 原因 | access_log | +|---|---|---| +| `/ws/` | WebSocket token 鉴权 | `off` | +| `/api/v1/auth/login` | 密码登录 | `off` | +| `/api/v1/auth/refresh` | token 刷新 | `off` | +| `/api/v1/h5/oauth/callback` | OAuth2 回调 | `off` | +| `/api/v1/wecom/callback` | 企微回调(验证 URL 含 echostr) | `off` | +| `/api/v1/agents/login` | 坐席登录 | `off` | +| `/api/v1/upload*` | 文件上传(可能含敏感文件名) | `off` | +| `/health` `/healthz` `/readyz` | 健康检查(高频) | `off` | + +## 4. 实现 + +```nginx +server { + # 全局 + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # WS(敏感) + location /ws/ { + access_log off; + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # 登录(敏感) + location ~ ^/api/v1/(auth|agents)/login$ { + access_log off; + proxy_pass http://backend; + } + + # 健康检查(高频) + location ~ ^/(health|healthz|readyz)$ { + access_log off; + proxy_pass http://backend; + } + + # 其它 + location / { + proxy_pass http://backend; + } +} +``` + +## 5. error_log 仍开启 + +⚠️ **error_log 仍开** —— 4xx/5xx 错误需要留痕(token 在 error log 里出现频率低,且 error log 有 TTL 自动切割)。 + +## 6. 日志清理脚本 + +`/etc/logrotate.d/nginx` 配: +``` +/var/log/nginx/*.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 0640 www-data adm + sharedscripts + prerotate + if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ + run-parts /etc/logrotate.d/httpd-prerotate; \ + fi + endscript + postrotate + invoke-rc.d nginx rotate >/dev/null 2>&1 + endscript +} +``` + +## 7. 风险与缓解 + +| 风险 | 缓解 | +|---|---| +| 漏关某个敏感路径 | 定期审计(任务 W-5,workbuddy 跑) | +| 调试时无 access_log 难定位 | debug 时临时开 `access_log /tmp/debug.log;` | +| 攻击者利用关闭日志 | error_log 仍开,异常请求有记录 | diff --git a/docs/ADRs/ADR-004-Token不入文件-走wincred.md b/docs/ADRs/ADR-004-Token不入文件-走wincred.md new file mode 100644 index 0000000..2724dd1 --- /dev/null +++ b/docs/ADRs/ADR-004-Token不入文件-走wincred.md @@ -0,0 +1,101 @@ +# ADR-004: Token 不入文件,走 wincred 缓存 + +**状态**: ✅ 已采纳 +**日期**: 2026-06-14 +**决策者**: 宋献 + Claude 评审 +**关联**: [[风险跟踪表]] 第十二节 12.6 / 推送约定 + +--- + +## 1. 背景 + +之前 Gitea 推送 token 直接嵌入 `.git/config` 的 `origin.url`: +``` +url = https://ae236991c3d5...@ds923plus.tail58d872.ts.net/... +``` + +**风险**: +- ❌ token 明文落盘 +- ❌ token 失效后难更新(URL 整体换) +- ❌ 误 `git add .git/` 可能入仓(虽然 .git/config 本身不入仓,但 .git/ 目录其他文件可能) +- ❌ auto-classifier 拒绝重写 URL(防误操作) + +**事故**: 2026-06-14 workbuddy-claude token 失效后,`origin.url` 残留死凭据。 + +## 2. 决策 + +**`.git/config` 的 `origin.url` 只写用户名,token 走 git credential helper(wincred)缓存**。 + +## 3. 实现 + +### 3.1 配 remote URL(无 token) + +```bash +git remote add origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git +# 或修复现有: +git remote set-url origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git +``` + +### 3.2 配 credential helper + +`.git/config`: +```ini +[credential] + helper = manager # Windows = wincred / Linux = git-credential-manager +``` + +### 3.3 首次推(输一次 token) + +```bash +git push -u origin main +# 弹窗 → username 留空,password = token +# wincred 自动缓存 +``` + +### 3.4 换 token(必走) + +```bash +# 清旧缓存 +printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\n" | git credential reject + +# 存新缓存(一次性,token 在 heredoc 不入文件) +printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\npassword=NEW_TOKEN\n" | git credential approve + +# 验证 +git push origin main +# 应不弹窗 +``` + +## 4. workbuddy 推送同理 + +`.workbuddy/config.json` 是 workbuddy 自己的凭据存储(类比 .git/config),**入仓** ❌。 + +**正确做法**: +- `.workbuddy/config.json` 写用户名/URL/其他配置,**不写 token** +- workbuddy 启动时读 `gitea.token` 字段(从环境变量 / 启动参数传入) +- 或者 workbuddy 自己也用 git credential helper + +**已加 .gitignore**: +```gitignore +.workbuddy/config.json +.workbuddy/config.local.json +.workbuddy/*.token +.workbuddy/credentials* +.workbuddy/.env* +``` + +## 5. 优势 + +- ✅ token 不入文件(只入 wincred 系统密钥环) +- ✅ 换 token 简单(`credential reject` + `approve`) +- ✅ 不会误入仓 +- ✅ auto-classifier 不拒绝(无 token 写文件) + +## 6. 风险与缓解 + +| 风险 | 缓解 | +|---|---| +| wincred 缓存被读(本机攻击) | 操作系统级防护 + 强密码 + BitLocker | +| 跨设备不能用 wincred | Linux 用 `git-credential-manager`,Mac 用 `git-credential-osxkeychain` | +| 换电脑忘缓存 | `git credential approve` 一次性配置 | +| token 在环境变量 | 仍比文件安全 + CI 用 secret store | diff --git a/docs/Gitea部署指南.md b/docs/Gitea部署指南.md new file mode 100644 index 0000000..d3f4273 --- /dev/null +++ b/docs/Gitea部署指南.md @@ -0,0 +1,391 @@ +# Gitea 部署指南 + +**适用范围**: 企微 IT 智能服务台项目 +**维护人**: 宋献 + Claude 协作 +**最后更新**: 2026-06-14(卸载清空事件后) +**关联**: [[风险跟踪表]] 第十二节 / [[CONTRIBUTING]] / [[scripts/backup-gitea.sh]] + +--- + +## 📌 1. 部署环境 + +### 1.1 推荐方案:Synology 套件版 + +| 项 | 值 | 备注 | +|---|---|---| +| NAS | Synology DS923+ | DSM 7.2+ | +| 套件 | Gitea 1.22+ | 套件中心搜 "Gitea" | +| 端口 | 8418 (HTTP) | 避开 3000(被占) | +| 数据库 | SQLite3 | 单机够用,避免 MySQL 配置坑 | +| Tailscale | tail58d872.ts.net | Funnel 暴露给 workbuddy 沙箱 | +| 备份 | `scripts/backup-gitea.sh` | 每天 cron 3 点 | + +### 1.2 备选方案:Docker 容器版 + +```bash +sudo mkdir -p /volume1/docker/gitea/{data,config,repos} +sudo chown -R 1000:1000 /volume1/docker/gitea + +docker run -d \ + --name gitea \ + --restart always \ + -p 8418:3000 \ + -p 2222:22 \ + -v /volume1/docker/gitea/data:/data \ + -v /volume1/docker/gitea/config:/etc/gitea \ + -v /volume1/docker/gitea/repos:/data/git/repositories \ + gitea/gitea:1.22 +``` + +**优势**:升级/迁移灵活 +**劣势**:需要 Container Manager 知识,SSL/反向代理需手配 + +--- + +## 📌 2. 初始化(首次安装) + +### 2.1 创管理员 + +1. 浏览器 `http://:8418/` +2. 看到 **"首次安装,请注册管理员账号"** +3. 填: + - **管理员用户名**: `simon`(项目负责人) + - **邮箱**: 你常用邮箱 + - **密码**: 强密码(≥16 位,大小写+数字+特殊) +4. **立即登录** + +### 2.2 创仓 + +1. 右上角 `+` → **创建新的仓库** +2. 填: + - **所有者**: `simon` + - **仓库名**: `wecom_it_smart_desk` + - **可见性**: 私有(Private) + - **⚠️ 不勾** "使用 README 初始化" + - **⚠️ 不勾** "使用 .gitignore" + - **⚠️ 不勾** "使用 License" +3. **创建仓库** + +### 2.3 创 Access Token(workbuddy-claude 协作) + +1. 创 **workbuddy-claude** 普通用户(站点管理 → 用户) +2. 用 workbuddy-claude 登录 +3. 头像 → **设置** → **应用** → **管理 Access Token** +4. 创: + - 令牌名: `claude-push` + - 权限: ✅ `repository` ✅ `issue` ✅ `user` + +--- + +## 📌 3. 本地仓接入 + +### 3.1 配 remote(走 wincred,不嵌 token) + +```bash +cd D:\资料\03-项目开发\wecom_it_smart_desk +git remote add origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git +``` + +### 3.2 首次推 main + +```bash +# 在 PowerShell 跑(会弹窗输 token) +git push -u origin main +``` + +**弹窗时**: +- **Username**: 留空 +- **Password**: 粘 access token + +**wincred 自动缓存**,后续 push 不弹窗。 + +**换 token**: +```bash +# 清旧缓存 +printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\n" | git credential reject + +# 存新缓存(在 bash 跑,token 在 heredoc) +printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=simon\npassword=NEW_TOKEN\n" | git credential approve +``` + +--- + +## 📌 4. Tailscale Funnel 暴露(给 workbuddy 沙箱) + +### 4.1 配 Funnel + +```bash +ssh simon@100.85.152.112 +sudo tailscale funnel --bg 8418 +``` + +### 4.2 验证 + +- 浏览器(任何网络)打开 `https://ds923plus.tail58d872.ts.net/` +- 应看到 Gitea 登录页 + +### 4.3 故障排查 + +| 现象 | 原因 | 解决 | +|---|---|---| +| Funnel 域名打不开 | tailscaled 停 | `sudo systemctl restart tailscaled` | +| 8418 connection refused | Gitea 套件停 | 套件中心 → Gitea → 启动 | +| TLS 证书报错 | Funnel HTTPS 证书未自动签 | 等 30 秒自动签,或 `sudo tailscale funnel reset` | + +--- + +## 📌 5. 分支保护(main 必须保护) + +### 5.1 用 simon admin token 跑 API + +```bash +TOKEN="simon的admin token" + +# 删旧保护 +curl -X DELETE \ + -H "Authorization: token $TOKEN" \ + "http://100.85.152.112:8418/api/v1/repos/simon/wecom_it_smart_desk/branch_protections/main" + +# 重建保护 +curl -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "branch_name": "main", + "enable_push": false, + "enable_pull_request": true, + "required_approvals": 1, + "dismiss_stale_approvals": true, + "block_admin_merge": false + }' \ + "http://100.85.152.112:8418/api/v1/repos/simon/wecom_it_smart_desk/branch_protections" +``` + +### 5.2 关键参数解释 + +| 参数 | 值 | 说明 | +|---|---|---| +| `enable_push` | `false` | 禁止直推 main | +| `enable_pull_request` | `true` | 需走 PR | +| `required_approvals` | `1` | 需 1 个 reviewer | +| `block_admin_merge` | `false` | simon 可强推(临时,等 workbuddy 接入改 true) | + +### 5.3 升级路径(workbuddy 接入后改严) + +- 创 `workbuddy-claude` 普通 user ✅ +- 创 workbuddy-claude token ✅ +- `block_admin_merge` 改 `true`(评审员有 ≥2 个 user) +- 加 `enable_status_check: true` + 配 Gitea Actions CI + +--- + +## 📌 6. 备份策略(必做!) + +### 6.1 首次部署备份脚本 + +```bash +# 本地仓 → NAS +scp scripts/backup-gitea.sh simon@100.85.152.112:/volume1/docker/wecom-it-desk/scripts/ + +# NAS 跑首次备份 +ssh simon@100.85.152.112 +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh +``` + +### 6.2 配 cron(每天 3 点) + +```bash +# 编辑 crontab +sudo crontab -e + +# 加一行 +0 3 * * * /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh >> /var/log/gitea-backup.log 2>&1 +``` + +### 6.3 验证 cron + +```bash +# 列出当前 cron +sudo crontab -l + +# 看下次执行时间 +systemctl list-timers | grep cron +``` + +### 6.4 备份内容 + +- ✅ 配置文件 `app.ini` +- ✅ SQLite 数据库(热备,`sqlite3 .backup`) +- ✅ 仓裸仓库 `repos/`(tar.gz 压缩) +- ✅ LFS 数据 +- ✅ 头像 / 附件 +- ✅ 元信息 `backup.meta` + +保留 **7 天**(`--keep 30` 改 30 天) + +### 6.5 异地备份(强烈建议) + +3 个方案: + +| 方案 | 成本 | 复杂度 | 推荐度 | +|---|---|---|---| +| A. 本机 D 盘 | 0 | 低 | ⭐⭐ | +| B. 阿里云 OSS / 腾讯云 COS | ~5 元/月 | 中 | ⭐⭐⭐ | +| C. NAS2 + OSS 双备 | ~10 元/月 | 高 | ⭐⭐⭐⭐ | + +**推荐 B**:在 NAS 装 `rclone`,cron 推 OSS。 + +--- + +## 📌 7. 恢复流程 + +### 7.1 列出可用备份 + +```bash +ls -lh /volume1/backups/gitea/ +# gitea-backup-20260614-180000.tar.gz +# gitea-backup-20260613-180000.tar.gz +# ... +``` + +### 7.2 恢复到 latest + +```bash +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh --restore latest +``` + +⚠️ **会自动停 Gitea → 覆盖数据 → 启动 Gitea** + +### 7.3 恢复到指定时间 + +```bash +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh --restore 20260614-180000 +``` + +### 7.4 验证恢复 + +1. 浏览器 `http://:8418/` → 应看到 Gitea 登录页 +2. 登录 simon → 应看到所有仓 +3. 选仓 → 应看到 commit 历史 + +--- + +## 📌 8. 卸载注意事项(血泪教训!) + +### 🛑 8.1 **卸载前必做** + +1. ✅ 跑 `scripts/backup-gitea.sh` 取最新备份 +2. ✅ scp 备份到本地 + OSS 异地 +3. ✅ 记下当前 Gitea 版本(套件中心看) +4. ✅ 记下当前端口(默认 3000 / 容器 8418 / 套件 8418) + +### 🛑 8.2 卸载"清空" vs "保留" + +| 选项 | 清什么 | 不清什么 | 适用 | +|---|---|---|---| +| **仅卸载** | 套件 app | app.ini / DB / repos / LFS | 临时停用 | +| **卸载并清空** ⚠️ | 套件 app + app.ini + DB | ⚠️ **可能**保留 repos / LFS(套件机制) | 永久删除 | + +### 🛑 8.3 卸载"清空"后仓还在硬盘 + +**2026-06-14 实战**:卸载清空后,`/volume1/@appdata/gitea/gitea/repos/` 仓裸仓库**还在**。 + +**恢复路径**: +1. 重装 Gitea 套件 +2. 初始化(simon's admin) +3. **不要** Web 创仓(会报"已存在文件") +4. SSH 删残留仓目录: + ```bash + sudo rm -rf /volume1/@appdata/gitea/gitea/repos/simon/wecom_it_smart_desk.git + sudo rm -rf /volume1/@appdata/gitea/gitea/lfs/simon/wecom_it_smart_desk.git + ``` +5. Web 创仓(空) +6. 本地推 main(本地仓有完整 11 commit) + +### 🛑 8.4 卸载"清空"后仓真的没了 + +**走备份恢复**: +```bash +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh --restore latest +``` + +--- + +## 📌 9. 故障排查 + +### 9.1 套件启动失败 + +```bash +# 看套件日志 +sudo cat /var/log/packages/Gitea.log + +# 重启套件 +sudo synopkg restart Gitea +``` + +### 9.2 端口 8418 被占 + +```bash +# 查谁占 +sudo lsof -i :8418 + +# 改 Gitea 端口(套件不支持改,需 Docker 版) +``` + +### 9.3 Funnel 不通 + +```bash +# 看 tailscale 状态 +sudo tailscale status + +# 看 Funnel 配置 +sudo tailscale funnel status + +# 重置 Funnel +sudo tailscale funnel reset +sudo tailscale funnel --bg 8418 +``` + +### 9.4 推 Gitea 401/403 + +```bash +# 清旧 wincred 缓存 +printf "protocol=https\nhost=ds923plus.tail58d872.ts.net\nusername=USER\n" | git credential reject + +# 重试 push,弹窗输新 token +git push -u origin main +``` + +### 9.5 推 Gitea 404 + +- 仓不存在 → 创仓 +- remote URL 错 → `git remote -v` 检查 +- workbuddy user 没访问权限 → 站点管理 → 用户 → 改权限 + +--- + +## 📌 10. 关联资源 + +- **风险跟踪表**: `docs/风险跟踪表.md` 第十二节(Gitea 重建复盘) +- **贡献指南**: `CONTRIBUTING.md`(commit 规范 + PR 流程) +- **备份脚本**: `scripts/backup-gitea.sh` +- **预检脚本**: `scripts/pre-commit-check.sh` +- **workbuddy 任务清单**: `.workbuddy/memory/2026-06-14-批量任务.md` +- **workbuddy 满载任务**: `.workbuddy/memory/2026-06-14-今夜-满载任务.md` +- **Tailscale 私网**: `tail58d872.ts.net` +- **Funnel 域名**: `https://ds923plus.tail58d872.ts.net` + +--- + +## 📌 11. 紧急联系 + +| 场景 | 联系人 | +|---|---| +| Gitea 套件问题 | 群晖技术支持(synology.com/support) | +| Tailscale Funnel | Tailscale 文档(tailscale.com/kb) | +| Token / 推送问题 | 项目负责人 宋献 | +| 仓数据丢失 | 走备份恢复(第 7 节) | + +--- + +*本指南是 2026-06-14 卸载清空事件的产物,目的是不再让类似事件发生* diff --git a/docs/SOPs/SOP-001-Gitea部署.md b/docs/SOPs/SOP-001-Gitea部署.md new file mode 100644 index 0000000..02b3942 --- /dev/null +++ b/docs/SOPs/SOP-001-Gitea部署.md @@ -0,0 +1,96 @@ +# SOP-001: Gitea 部署标准作业流程 + +**适用**: 新机器 / NAS 迁移 / Gitea 重建 +**耗时**: 30-45 分钟 +**关联**: [[Gitea部署指南]] / [[ADR-001]] + +--- + +## 1. 前置检查 + +```bash +# 1.1 NAS 可达 +ping 100.85.152.112 + +# 1.2 SSH 通 +ssh simon@100.85.152.112 + +# 1.3 Tailscale 状态 +sudo tailscale status + +# 1.4 端口 8418 未占 +sudo lsof -i :8418 +``` + +## 2. 装 Gitea 套件 + +1. DSM → 套件中心 +2. 搜 `Gitea` → 安装 +3. 装好跳 `http://100.85.152.112:8418/` + +## 3. 初始化 + +1. 创管理员: + - 用户名: `simon` + - 邮箱: 你的 + - 密码: 强密码(≥16 位) +2. 数据库: 选 **SQLite3** +3. 站点名: `企微 IT 智能服务台 Git` +4. 立即登录 + +## 4. 创仓 + token + +1. 创仓 `wecom_it_smart_desk`(不勾 README 初始化) +2. 创 simon access token(`simon-admin`) +3. 创 workbuddy-claude user + token(`claude-push`) + +## 5. 配 Tailscale Funnel + +```bash +sudo tailscale funnel --bg 8418 +# 验证 +curl -I https://ds923plus.tail58d872.ts.net/ +``` + +## 6. 配分支保护 + +见 [[ADR-001]] §5 + `scripts/branch-protection.sh`(待写) + +## 7. 部署备份 + +```bash +# 推备份脚本 +scp scripts/backup-gitea.sh simon@100.85.152.112:/volume1/docker/wecom-it-desk/scripts/ + +# 配 cron +ssh simon@100.85.152.112 +sudo crontab -e +# 加: 0 3 * * * /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh +``` + +## 8. 本地仓接入 + +```bash +cd D:\资料\03-项目开发\wecom_it_smart_desk +git remote add origin https://simon@ds923plus.tail58d872.ts.net/simon/wecom_it_smart_desk.git +git push -u origin main # 弹窗输 token +``` + +## 9. 验证清单 + +- [ ] Gitea Web UI 正常 +- [ ] Funnel 域名正常 +- [ ] 创仓 + token 完成 +- [ ] 分支保护已配 +- [ ] 备份 cron 已配 +- [ ] 本地 push 成功 +- [ ] workbuddy-claude user 已创 + token 已配 + +## 10. 出错回滚 + +| 现象 | 解决 | +|---|---| +| 8418 端口冲突 | Docker 版用 3000 端口 | +| SQLite 写失败 | 检查 `/volume1/@appdata/gitea` 权限 | +| Funnel 域名不通 | `sudo tailscale funnel --bg 8418` 重试 | +| 推 Gitea 401 | 清 wincred,重输 token | diff --git a/docs/SOPs/SOP-002-Gitea备份恢复.md b/docs/SOPs/SOP-002-Gitea备份恢复.md new file mode 100644 index 0000000..3423dbf --- /dev/null +++ b/docs/SOPs/SOP-002-Gitea备份恢复.md @@ -0,0 +1,97 @@ +# SOP-002: Gitea 备份恢复标准作业流程 + +**适用**: 数据丢失应急 / 误操作回滚 / 异地迁移 +**耗时**: 5-15 分钟 +**关联**: [[Gitea部署指南]] §6/§7 + +--- + +## 1. 备份策略 + +| 项 | 值 | 备注 | +|---|---|---| +| 频率 | 每天 3 点 | cron | +| 保留 | 7 天 | 默认 | +| 路径 | `/volume1/backups/gitea/` | NAS 本地 | +| 异地 | OSS / COS 推 | M-1 风险,待解决 | +| 工具 | `scripts/backup-gitea.sh` | 已写 | + +## 2. 手动备份(应急) + +```bash +ssh simon@100.85.152.112 +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh +``` + +输出: +``` +[INFO] === Gitea 备份开始 === +[OK] 备份配置 app.ini +[OK] SQLite 热备完成 +[OK] 仓库 tar 完成 +[INFO] === 备份完成 === +[OK] 最终备份: gitea-backup-20260615-030000.tar.gz +``` + +## 3. 列出可用备份 + +```bash +ls -lh /volume1/backups/gitea/ +# gitea-backup-20260614-180000.tar.gz 500M +# gitea-backup-20260613-180000.tar.gz 495M +# gitea-backup-20260612-180000.tar.gz 490M +``` + +## 4. 恢复到 latest + +```bash +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh --restore latest +``` + +**会做**: +1. 停 Gitea 套件 +2. 解压备份 +3. 覆盖 app.ini / SQLite / repos +4. 启动 Gitea 套件 + +⚠️ 5 秒倒计时,Ctrl+C 取消 + +## 5. 恢复到指定时间 + +```bash +# 看时间戳 +ls /volume1/backups/gitea/ | grep gitea-backup +# gitea-backup-20260614-180000.tar.gz + +# 恢复 +sudo bash /volume1/docker/wecom-it-desk/scripts/backup-gitea.sh --restore 20260614-180000 +``` + +## 6. 验证恢复 + +1. `http://100.85.152.112:8418/` → 登录 simon +2. 选仓 → 看 commit 历史 +3. 验证仓裸仓库大小(`du -sh /volume1/@appdata/gitea/gitea/repos/`) +4. 验证 LFS 数据 + +## 7. 异地推 OSS(待配) + +```bash +# NAS 装 rclone +sudo apt install rclone # 或 synology 套件版 + +# 配 OSS +rclone config +# 选 aliyun OSS / 腾讯云 COS + +# 加 cron +0 4 * * * rclone copy /volume1/backups/gitea/ remote:gitea-backup/ --include "gitea-backup-*.tar.gz" +``` + +## 8. 故障排查 + +| 现象 | 原因 | 解决 | +|---|---|---| +| 备份文件大小 0 | SQLite .backup 失败 | 改用文件复制模式(脚本已支持) | +| 恢复后启动失败 | 数据不一致 | 试更早的备份 | +| LFS 数据丢 | 备份脚本漏 LFS | 升级脚本(已修) | diff --git a/docs/SOPs/SOP-003-推送评审.md b/docs/SOPs/SOP-003-推送评审.md new file mode 100644 index 0000000..874ba37 --- /dev/null +++ b/docs/SOPs/SOP-003-推送评审.md @@ -0,0 +1,134 @@ +# SOP-003: 推送评审标准作业流程 + +**适用**: 任何 commit 推 Gitea / PR 评审 / workbuddy 推送 +**耗时**: 5-15 分钟 +**关联**: [[CONTRIBUTING]] / [[scripts/pre-commit-check.sh]] / [[风险跟踪表]] 第九/十/十一节 + +--- + +## 1. 推送前自检(4 件套) + +```bash +cd D:\资料\03-项目开发\wecom_it_smart_desk + +# 必跑 +bash scripts/pre-commit-check.sh --branch + +# 严格模式(任何 warn 失败) +bash scripts/pre-commit-check.sh --branch --strict +``` + +**通过标准**: +- ✅ PASS ≥ 检查项数 +- ⚠️ WARN 看是否影响评审 +- ❌ FAIL 必修 + +## 2. Commit 规范 + +格式: `(): ` + +| type | 用途 | +|---|---| +| `feat` | 新功能 | +| `fix` | Bug 修复 | +| `refactor` | 重构(无新功能 / 无 Bug 修复) | +| `docs` | 文档 | +| `chore` | 构建/工具/依赖 | +| `security` | 安全 | +| `perf` | 性能 | +| `test` | 测试 | + +**subject**: 中文,祈使句,≤50 字 +**body**: 详细说明,每行 ≤72 字 +**footer**: 关联 Issue / workbuddy 任务 + +## 3. 推送流程 + +### 3.1 workbuddy 推送 + +1. workbuddy 客户端启动 → 读 `config.json` + `memory/` +2. 接任务(W-1 / W-2 / ...) +3. 写代码 → 本地 commit +4. 推 `feature/xxx` 分支(不走 main,需 PR) +5. 通知 Claude 评审 + +### 3.2 simon 推送(自己改) + +1. 本地改 + commit +2. 推 `feature/xxx` 分支 +3. Gitea Web 开 PR +4. 自己 approve + merge(因 `block_admin_merge: false`) + +## 4. 评审流程 + +### 4.1 Claude 评审(主) + +1. 收到 workbuddy 推送通知 +2. Read 文件 + diff +3. 检查 4 件套 +4. 写评审报告 `docs/评审报告/workbuddy-{date}-{topic}.md` +5. 评级: + - 🟢 通过 → 通知合并 + - 🟡 留 P1/P2 修 → 评审报告列遗留 + - 🔴 拒绝 → 评审报告列阻断 + +### 4.2 simon 合并 + +1. 评审通过 → Gitea Web 合并 PR +2. 触发 Gitea Actions CI(待配) +3. CI 绿 → 删 feature 分支 + +## 5. 评审失败处理 + +| 评级 | 处理 | +|---|---| +| 🟢 通过 | 合并 + 部署 | +| 🟡 留 P1 | 合并 + 写遗留表 + workbuddy 下一轮修 | +| 🔴 拒绝 | workbuddy 修 → 重新评审 | + +## 6. 评审报告格式 + +`docs/评审报告/workbuddy-{YYYY-MM-DD}-{topic}.md`: + +```markdown +# 评审: {topic} + +**推送日期**: {date} +**评审日期**: {date} +**评审人**: Claude +**关联 PR**: feature/xxx → main +**关联 commit**: N 个 + +## ⭐ 一句话结论 +... + +## 📊 评审结果 +| # | 项 | 评级 | 备注 | +|---|---|---|---| + +## ✅ 已正确完成 +... + +## 🟡 半成品(留 P2 优化) +... + +## ❌ 错误 +... + +## 📁 变更清单(N commit) +... + +## 🔄 下一轮任务清单 +... + +## 🔗 推 Gitea 状态 +- 远端分支: feature/xxx (HEAD = xxx) +- 评审: ✅ 通过 / 🟡 通过 + 留 / 🔴 拒绝 +``` + +## 7. 不允许 + +- ❌ 跳过评审直推 main +- ❌ 评审失败强行合并 +- ❌ 评审未消化前叠加新功能 +- ❌ 改评审报告原文(只加节) diff --git a/docs/SOPs/SOP-004-应急响应.md b/docs/SOPs/SOP-004-应急响应.md new file mode 100644 index 0000000..35d712d --- /dev/null +++ b/docs/SOPs/SOP-004-应急响应.md @@ -0,0 +1,208 @@ +# SOP-004: 应急响应标准作业流程 + +**适用**: P0 漏洞 / 数据丢失 / 服务中断 / 安全事件 +**响应时间**: 5 分钟响应 + 30 分钟止血 + 24 小时根因 +**关联**: [[风险跟踪表]] / [[CONTRIBUTING]] §紧急修复 + +--- + +## 1. 事件分级 + +| 等级 | 场景 | 响应时间 | +|---|---|---| +| 🔴 **P0 紧急** | P0 鉴权漏洞 + 数据泄露 + 服务全停 | 5 min | +| 🟠 **P1 高** | P1 功能故障 + 单服务降级 | 30 min | +| 🟡 **P2 中** | P2 性能 / UI 问题 | 4 h | +| 🟢 **P3 低** | 体验优化 | 1 周 | + +## 2. P0 应急流程(5 min 响应) + +### 2.1 立即止血 + +1. **服务降级**: + - 关闭外网访问:`sudo iptables -A INPUT -p tcp --dport 8418 -j DROP` + - 或:套件中心停 Gitea + - 或:Nginx `deny all;` +2. **停可疑服务**: + - 停后端:`docker compose stop backend` + - 停 WebSocket:`docker compose stop nginx`(整体停) +3. **保留现场**: + - 不删任何文件 + - 复制 log 到 `/tmp/incident-{timestamp}/` + - 截图 + +### 2.2 通知 + +1. 微信 / 电话通知项目负责人 宋献 +2. 邮件群发:`wecom-it-desk-incident@servyou-it.com` +3. 建应急群 + +### 2.3 临时回滚 + +```bash +# 1. 找上一个稳定版本 +git tag -l # 看 release tag +git log --oneline -20 # 看 commit 历史 + +# 2. 回滚到上一个 commit +git revert HEAD # 生成新 commit 撤销 +# 或 +git reset --hard HEAD~1 # 强回滚(慎用) + +# 3. 强推(临时,需 admin 权限) +git push -f origin main +``` + +## 3. 根因分析(24h 内) + +### 3.1 收集证据 + +```bash +# 后端日志 +docker logs backend --tail 1000 > /tmp/incident/backend.log + +# nginx 错误日志 +sudo cat /var/log/nginx/error.log > /tmp/incident/nginx-error.log + +# Gitea 日志 +sudo synopkg log Gitea > /tmp/incident/gitea.log +``` + +### 3.2 5 Why 分析 + +```markdown +# 5 Why 分析 + +**事件**: 坐席登录无鉴权 +**Why 1**: agents.py login() 函数没用 Depends(get_current_*) +**Why 2**: workbuddy 加新端点时没跑 pre-commit-check +**Why 3**: pre-commit-check 不在 git commit hook 里 +**Why 4**: 没用 pre-commit 框架(只是脚本) +**Why 5**: 流程规范没强制(评审可跳) + +**根因**: 流程规范未自动化 +**对策**: 加 pre-commit + Gitea Actions 强制 +``` + +### 3.3 写事故报告 + +`docs/事故报告/incident-{date}-{topic}.md`: + +```markdown +# 事故报告: {topic} + +**日期**: {date} +**等级**: 🔴 P0 +**响应人**: {name} +**持续**: X 分钟 + +## 1. 时序 +| 时刻 | 事件 | +|---|---| + +## 2. 影响范围 +- 用户: X 人受影响 +- 数据: 是否泄露 +- 服务: 停 X 分钟 + +## 3. 5 Why 根因 +... + +## 4. 修复 commit +- {commit-hash} +- {commit-message} + +## 5. 防止再发 +- [ ] 加 pre-commit hook +- [ ] 加 Gitea Actions 强制 +- [ ] 更新风险跟踪表 +- [ ] 评审 SOP 更新 +``` + +## 4. P1 应急流程(30 min 响应) + +### 4.1 评估 + +- 是否影响生产用户? +- 是否有降级方案? + +### 4.2 止血 + +- 单服务降级(关问题服务,其它继续) +- 临时禁用相关端点(nginx `location /api/v1/xxx { return 503; }`) + +### 4.3 修复 + +- hotfix 分支(从 main 拉) +- PR + 评审 + 合并 + 部署 + +## 5. 数据丢失应急 + +### 5.1 Gitea 数据丢失 + +1. **别再操作** Gitea(避免覆盖) +2. 跑 `scripts/backup-gitea.sh --restore latest` +3. 验证:仓 commit 数 / token 列表 +4. 不行:试更早备份 + +### 5.2 生产数据库丢失 + +1. 立即停所有服务(避免写入) +2. 看 PostgreSQL 数据目录:`/var/lib/postgresql/data` +3. 走 PITR(Point In Time Recovery) +4. 启用只读模式 + 通知用户 + +## 6. 安全事件 + +### 6.1 Token 泄露 + +1. **立即撤销** token: + ```bash + curl -X DELETE -H "Authorization: token $ADMIN_TOKEN" \ + "http://100.85.152.112:8418/api/v1/users/{username}/tokens" + ``` +2. 清 wincred 缓存 +3. 创新 token + 配新凭据 +4. 改所有引用旧 token 的脚本/配置 +5. 评审日志:谁访问过 / 推过什么 + +### 6.2 入侵检测 + +1. 看 `auth.log` / `nginx-access.log` / `backend.log` +2. 找异常 IP / 时间 / 路径 +3. 封 IP:`sudo iptables -A INPUT -s {ip} -j DROP` +4. 改所有密码 / 凭据 +5. 走事件调查流程 + +## 7. 通讯模板 + +### 7.1 启动应急 + +``` +【应急启动】{事件简述} +等级: 🔴 P0 +影响: {用户/数据/服务} +已开始止血:{动作} +请相关人:{人名} 立即响应 +群: {微信群名} +``` + +### 7.2 解决通知 + +``` +【已解决】{事件简述} +持续: X 分钟 +修复: {commit-hash} +根因: {5 Why 结论} +防止再发: {动作} +报告: docs/事故报告/{file}.md +``` + +## 8. 联系 + +| 角色 | 联系人 | +|---|---| +| 项目负责人 | 宋献(企业微信 / 手机) | +| 运维 | IT 支持组 | +| NAS / Gitea | 群晖技术支持 | +| Tailscale | tailscale.com/support |