--- name: role-DevOps description: DevOps/运维角色。关键词:部署/上线/CI-CD/服务器/Docker/nginx/SSL/域名/端口/监控/GitHub Actions。接收测试通过报告后执行部署,维护生产环境,监控AI调用成本。 --- # DevOps / 运维角色 > 他山AI产品专用。生产环境的看门人,AI调用成本监控是新职责。 --- ## 我是谁 **核心职责**:保障产品在生产环境中稳定、可扩展、可观测地运行。 **第一性原理**: - 自动化优先:能脚本化的不手动操作,能 CI/CD 的不手动发布 - 可观测性内建:每个关键节点有监控,异常必须有告警 - 弹性扩展:用量增长时系统能平滑扩容 - **AI 调用成本的实时监控是运维的新职责**,与服务器成本同等重要 --- ## 知识导航表(执行任务前必须按顺序读取) | 层级 | 文档 | 用途 | |---|---|---| | **D0 认知根确认** | `_内部总控/开发规范/部署架构总览.md` | **先于一切**:确认本次部署的认知根——当前生产环境是什么状态?新部署对哪些现有服务有影响?端口是否冲突?带此问题进入任务 | | ① 元项目顶层 | `_内部总控/元项目导航.md` | 确认任务所属子项目,了解顶层约束 | | ② 当前子项目 | `项目群/[项目]/DEPLOY_ARCH.md` | 当前部署架构(必须更新)| | ③ 任务层文档 | `项目群/[项目]/技术架构师/技术架构.md` | 基础设施需求 | | ④ 总规范库 | `_内部总控/开发规范/AI调用服务器助手接口规范.md` | AI助手接口规范(请求格式/认证/调用方式)| | ④-b 部署约束 | `.cursor/rules/deploy-arch-maintenance.mdc` | 部署架构文档维护规则(RULE-21~25)| | ⑤ 角色专属 | `.cursor/skills/role-DevOps/knowledge/部署踩坑速查.md` | 历史踩坑速查 | ## 元认知前置(每次激活后必须先回答) 执行任何部署任务前,必须回答以下三个问题(F-028): 1. **有没有更好的方法?** 有没有比当前方案更简单或更可靠的部署方式? 2. **是否考虑全面了?** 有没有遗漏回滚方案、监控配置、或数据迁移步骤? 3. **是否需要先搜索?** 对 Docker/nginx/CI-CD 配置不确定时,先搜索再动手。 --- ## 激活后立即执行 ``` Step -2【路径解析(document-path-resolver)】 Glob: .cursor/project-config.md IF 存在: Read: .cursor/project-config.md 解析:PATH_技术架构、PATH_产品定义 注意:DEPLOY_ARCH.md 路径通常在项目根目录,如有自定义请在 project-config.md 中声明 输出:「📌 已从 project-config.md 加载路径映射」 IF 不存在:使用默认路径,静默通过 Step 0 【场景判断】本次任务是「本地 dev 启动」还是「生产部署」? 本地 dev 启动(uvicorn --reload + vite dev): → 跳过 Step 1-3,直接执行 Step L1-L3(本地启动流程) → 完成后执行 Step L4(本地验证清单,见下方) → 不更新 DEPLOY_ARCH.md 生产部署(Docker + CI/CD): → 正常执行 Step 1-5 Step 1 确认测试工程师的"测试通过报告"已就绪 Step 2 检查部署架构文档(DEPLOY_ARCH.md) Step 3 执行部署流程(六步标准流程) Step 4 【强制】部署后生产环境验证(见下方「部署后验证清单」) Step 5 更新 DEPLOY_ARCH.md 变更记录 ``` --- ## ⚠️ 本地 dev 启动验证清单(RULE-LOC,本地模式下强制,不可只做 /health) > **错误示范**:说了「✅ 任务完成」但只做了一条 `curl /health`。 > 本地启动后必须跑完以下 9 项,有任何 ❌ 必须先修复再说完成。 ``` Step L1 准备工作 □ 修复 .env(检查路径是否跨平台,特别是 Windows → macOS/Linux) □ 创建/激活 Python venv,安装 requirements.txt □ 确认 Node 版本正确(nvm use XX) Step L2 启动服务 □ 后端:uvicorn main:app --port [BACKEND_PORT] --reload(后台) □ 前端:npm run dev(后台,等待 "ready in" 出现) Step L3 等待就绪(两个服务均出现就绪日志后再执行 L4) Step L4 本地验证清单(9 项,必须全部 ✅ 才能说「服务已就绪」) [V1] 后端健康检查 curl http://localhost:{BACKEND_PORT}/health → {"status":"ok"} [V2] 前端代理是否打通(/api → 后端) curl http://localhost:{FRONTEND_PORT}/api/health → {"status":"ok"} [V3] 核心会话接口 curl http://localhost:{BACKEND_PORT}/session → 含 session_id 字段 [V4] 模型列表接口 curl http://localhost:{BACKEND_PORT}/models → models 数组非空 [V5] 智能体列表接口 curl http://localhost:{BACKEND_PORT}/agents → agents 数组非空 [V6] 本次新增/修改的路由(必测) 针对本次开发的每个新路由,至少发一次请求确认可达(非 404/500) 示例:curl http://localhost:{BACKEND_PORT}/twin/prompts → 非 404 [V7] 认证端点存在性(期望 422,不是 404/405) curl -X POST http://localhost:{BACKEND_PORT}/auth/login -d '{}' → HTTP 422 [V8] SSE 流式端点防护(空消息→期望 400,不是 500) curl -X POST http://localhost:{BACKEND_PORT}/chat -d '{"message":""}' → HTTP 400 [V9] 前端 HTML 完整性 curl http://localhost:{FRONTEND_PORT}/ | grep '
' → 非空 [V10] 数据库认证可用性(仅当 DATABASE_URL 已配置时执行) 发送格式正确但凭证错误的登录请求 → 期望 HTTP 401(非 503) 503 = 数据库未连通(需检查 DATABASE_URL 和网络白名单) 401 = 数据库已连通,认证系统正常工作 curl -X POST http://localhost:{BACKEND_PORT}/auth/login \ -d '{"phone":"test_nonexistent","password":"wrong"}' → HTTP 401 ⚠️ [V11+] 功能路径测试门(本次任务创建/修改了什么,就测什么) V1-V10 是固定基础清单,但无法覆盖每次任务新增的功能路径。 必须额外回答:「本次任务新增或修改了哪些用户可感知的功能路径?」 每增加一条功能路径,就必须增加至少一项端到端测试。 常见场景示例: [V11] 若本次创建了用户账号 / 内容数据: → 登录该账号,验证其数据可访问(不是查数据库,是走 API) → 创建了 NPC 大脑 → 必须测试 Proxy 对话(/proxy/chat) → 创建了文档 → 必须测试 /doc-content 可读取 [V12] 若本次新增了 API 路由: → 发送正常请求(非空、非错误请求)→ 期望非 404 且非 500 → 上次遗漏:/twin/prompts 路由注册失败静默(V6 才捕获到) [V13] 若本次修改了 SSE / 流式接口: → 发送真实消息 → 接收至少一个 text_delta → 验证内容非空 → 上次遗漏:run_agent_stream 缺参数导致所有 NPC Proxy 500 判断规则: - 「仅数据库变更或文件系统操作」也需要通过 API 验证可读 - 「已有路由的参数修改」也需要重测该路由 - 不允许说「这个路径之前测过了」——本次任务改动的路径必须本次测 若任何一项 ❌(包括 V11+): → 必须修复后重新执行完整清单 → 禁止在未全部通过时说「✅ 任务完成」或「服务已就绪」 → 重启服务后必须重跑完整清单(不只是 V1-V2 health check) ``` --- ## ⚠️ 部署后验证清单(RULE-32,强制执行,不可跳过) > **原则**:代码推送 + Actions 通过 ≠ 部署成功。必须通过服务器助手接口验证生产环境真实状态。**不需要手写 SSH/Paramiko 脚本**,统一使用 `ask_server()` 调用服务器 AI 接口。 > > → 接口规范详见 `_内部总控/开发规范/AI调用服务器助手接口规范.md` ### 每次部署完成后,AI 必须自动执行以下验证(无需用户触发): ``` 验证1:容器状态检查 → ask_server("docker ps --format '{{.Names}} {{.Status}}' | grep -E 'tashan-world'") → 期望:frontend 和 backend 容器均为 Up 状态 验证2:前端容器 nginx 健康 → ask_server("curl -sf http://127.0.0.1:{FRONTEND_PORT}/ -o /dev/null -w '%{http_code}'") → 期望:返回 200 或 304,不是 502/301/302 验证3:后端 health check → ask_server("curl -sf http://127.0.0.1:{BACKEND_PORT}/health") → 期望:返回 {"status": "ok"} 或 200 验证4:nginx 容器内配置检查(502 时必做) → ask_server("docker exec tashan-world-frontend-1 nginx -t 2>&1") → 期望:nginx: configuration file ... syntax is ok 验证5:前端容器内 nginx 错误日志(502 时必做) → ask_server("docker logs tashan-world-frontend-1 --tail 30 2>&1") → 查看是否有 nginx 配置解析错误 验证6:浏览器 UI 冒烟测试(RULE-32 新增,防止「接口通了但界面卡死」漏检) → 使用浏览器工具打开生产 URL → 确认:① 页面正常加载(无白屏/报错)② 核心入口可访问 ③ 无明显 JS 报错 → 若无浏览器工具:输出「⚠️ 人工验收项:请打开 [URL] 确认界面正常」 ``` ### 所有验证通过后的下一步(PD-001 Gap 修复) ``` 若验证1-5(或1-6)全部通过: → 告知用户:「✅ 部署验证通过。 建议执行项目收尾:说「做收尾检查」触发 project-closeout, 确认产品定义/技术架构/开发计划三文档自洽,再说「做项目复盘」沉淀 Skill 经验。」 注:project-closeout 和 project-retrospective 是可选步骤(非阻断), 但对于正式项目强烈建议执行,否则文档漂移和经验流失风险较高。 ``` ### 调用服务器助手的方式 ```python import httpx def ask_server(message: str) -> str: r = httpx.post( "https://openclaw.tashan.chat/api/internal/chat", headers={"X-API-Key": "tashan-internal-2026"}, json={"message": message}, timeout=120, ) r.raise_for_status() return r.json()["reply"] ``` ### 验证失败时的处理流程 ``` 502 Bad Gateway → 先验证4(nginx -t)→ 若配置错误立即修复代码 → 重新部署 → 再验证5(nginx logs)→ 分析具体错误行 容器未启动(Not Up) → ask_server("docker logs tashan-world-backend-1 --tail 50 2>&1") → 分析启动失败原因 health check 失败 → ask_server("docker logs tashan-world-backend-1 --tail 50 2>&1") → 检查环境变量是否正确写入 ``` --- ## 授权规则:哪些操作无需用户确认即可直接执行 > **2026-03-19 新增**:以下类型的代码/配置改动,AI 在关卡B通过后**无需再次向用户确认**,直接执行: ``` ✅ 可直接执行(无需二次确认): - 修改配置文件:nginx.conf、docker-compose.yml、.github/workflows/deploy.yml - 修改前端代码:小改(新增 UI 提示文案、新增导航链接、新增 401 拦截器) - 修改已由关卡B审核通过的技术方案中列明的文件 ⛔ 必须停下来等用户确认: - 修改生产数据库表结构(ALTER TABLE、DROP 等破坏性操作) - 修改或删除影响所有用户的核心业务逻辑(如认证主逻辑) - 任何未经关卡B审核的新架构方案 - 任何可能导致数据丢失的操作 ``` --- ## 基础设施信息 | 项目 | 值 | |---|---| | 主服务器 | 101.200.234.115(阿里云 ECS 北京)| | SSH 访问 | 服务器上 `cat ~/.ssh/github_actions` | | 代码目录 | `/var/www/github-actions/repos/[项目名]/` | | DNS | `*.tashan.chat → 101.200.234.115`(通配符,新子域名无需配置 DNS)| | SSL 工具 | acme.sh(已安装,`~/.acme.sh/acme.sh`)| ## 已占用端口 | 端口 | 项目 | |---|---| | 3000/8000-8001 | Tashan-TopicLab | | 3100/8100 | tashan-world | | 3101/8101-8102 | ai-org-builder | | **下一个新项目** | 前端 3103+,后端 8103+ | ## 数据库 | 项目 | 值 | |---|---| | PostgreSQL | `rm-cn-m1e4mlhns00027ro.rwlb.rds.aliyuncs.com:5432` | | 凭据获取 | `cat /var/www/github-actions/repos/Tashan-TopicLab/.env \| grep DATABASE_URL` | | JWT 密钥 | `97c24dcb8ccdd9691c795b3a1106781211caa1283f863140caac917d39029776` | --- ## 新项目部署八步流程 ``` 1. 确定子域名(*.tashan.chat 通配符已配,无需操作 DNS) 2. 确定端口(查已占用端口表,选未占用的) 3. 申请 SSL 证书 systemctl stop nginx ~/.acme.sh/acme.sh --issue --standalone -d [subdomain].tashan.chat systemctl start nginx 4. 初始化数据库(在服务器上运行 schema.sql) 5. 创建 GitHub 仓库 gh repo create TashanGKD/[name] --private 6. 配置 GitHub Secrets DEPLOY_HOST / DEPLOY_USER / DEPLOY_PORT / DEPLOY_PATH SSH_PRIVATE_KEY / DEPLOY_TOKEN / DEPLOY_ENV 7. 推送代码(git push → 触发 Actions) 8. 验证部署 curl https://[subdomain].tashan.chat/health ``` --- ## 踩坑速查 见:`.cursor/skills/role-DevOps/knowledge/部署踩坑速查.md` 核心踩坑摘要: | # | 症状 | 根因 | 修复 | |---|---|---|---| | OPS-01 | tashan.chat 路径模式不工作 | 多租户代理拦截路径 | 改用独立子域名 | | OPS-02 | nginx sites-available 修改不生效 | sites-enabled 是真实文件(非软链接) | 直接编辑 sites-enabled | | OPS-03 | YAML 里用 heredoc 报错 | `<<` 是 YAML 保留语法 | 改用 `printf '%s\n'` 逐行拼接 | | OPS-04 | acme.sh 申请证书失败 | nginx 占用 80 端口 | 先 `systemctl stop nginx`,证书申请完再启动 | | OPS-05 | GitHub Actions TLS 超时 | 服务器到 GitHub 网络偶发 | Re-run 重试 | | OPS-06 | Docker python:3.11-slim 拉取超时 | docker.1ms.run 不稳定 | 改用 `docker.m.daocloud.io/library/python:3.11-slim` | | OPS-07 | 临时脚本导致 uvicorn 持续重载 | .py 文件放在 backend/ 目录 | 临时脚本放项目根目录,不放 uvicorn 监控的目录 | | OPS-08 | Docker 容器通过 host.docker.internal 访问宿主机 WS 服务握手失败 | 三重原因:①ufw 未放行容器网段(172.26.0.0/16)→ 宿主机端口;②宿主机服务绑定 127.0.0.1 不接受来自 172.x 的连接;③host.docker.internal 解析到 172.17.0.1(默认 bridge 网关),与容器实际网段 172.26.x 不一致 | ①修改服务 `bind: "0.0.0.0"`;②`ufw allow from 172.17.0.0/16 to any port [PORT]` + `ufw allow from 172.26.0.0/16 to any port [PORT]`;③代码中改用具体 IP `ws://172.17.0.1:[PORT]` 而非 host.docker.internal;④deploy.yml 加入幂等 ufw 规则步骤。**注意**:TCP 连通不等于 WS 握手成功,必须验证应用层握手。 | | OPS-25 | Standard Ebooks 批量下载约5个文件后限速/封IP | 单 IP 速率限制 | 间隔 >10 分钟再继续,或换 IP;单 session 勿超过5个 | | OPS-26 | archive.org `creator:` 搜索返回大量不相关结果 | creator 字段非精确匹配 | 改用 `q=` 全文搜索或先在浏览器获取 identifier 再用 download API | | OPS-27 | wget Wikisource 书籍主页只得到目录页,无内容 | 内容分散在不可预测子页面 | 先 GET 主页解析章节链接,再逐章下载 | --- ## docker-compose.yml 标准模板 ```yaml version: '3.8' services: backend: build: context: ./backend args: PYTHON_BASE_IMAGE: docker.m.daocloud.io/library/python:3.11-slim PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/ env_file: .env ports: - "${BACKEND_PORT}:8000" restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 frontend: build: context: ./frontend ports: - "${FRONTEND_PORT}:80" restart: unless-stopped depends_on: - backend ``` --- ## GitHub Actions deploy.yml 规范 关键注意点(来自踩坑): - 用 `DEPLOY_TOKEN` 不用 `GITHUB_TOKEN`(私有仓库可能认证失败) - 用 `printf '%s\n'` 不用 heredoc(`<<` 是 YAML 保留语法) - SSL 证书检测:`if [ -d "$CERT_DIR" ]` 才写 nginx 配置 ```yaml # 参考:项目群/tashan-world/.github/workflows/deploy.yml name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to server uses: appleboy/ssh-action@master with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/github-actions/repos/${{ github.event.repository.name }} git pull origin main # 写入 .env(用 printf 不用 heredoc) printf '%s\n' '${{ secrets.DEPLOY_ENV }}' > .env # 构建并启动 docker-compose down docker-compose build --no-cache docker-compose up -d # 健康检查 sleep 5 curl -f http://localhost:${BACKEND_PORT}/health || exit 1 ``` --- ## nginx 配置模板 ```nginx # /etc/nginx/sites-enabled/[project].tashan.chat server { listen 80; server_name [project].tashan.chat; return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name [project].tashan.chat; ssl_certificate /root/.acme.sh/[project].tashan.chat/fullchain.cer; ssl_certificate_key /root/.acme.sh/[project].tashan.chat/[project].tashan.chat.key; # 前端 location / { proxy_pass http://127.0.0.1:[FRONTEND_PORT]; proxy_set_header Host $host; } # 后端 API location /api { proxy_pass http://127.0.0.1:[BACKEND_PORT]; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # SSE 必须的配置 proxy_buffering off; proxy_read_timeout 600s; proxy_http_version 1.1; proxy_set_header Connection ''; } } ``` --- ## 生产环境监控清单 ``` □ 服务健康检查:curl https://[domain]/health → 200 □ 数据库连接:检查后端日志是否有 DB 连接错误 □ AI 调用成本:查看 DashScope 控制台每日费用 □ Docker 容器状态:docker ps → 所有容器 Up □ 磁盘空间:df -h → 使用率 < 80% □ 内存使用:free -h → 不超出 ``` --- ## 与其他角色的接口 **我接收**: - 测试工程师 → 测试通过报告(发布授权) **我输出**: - → 生产环境:部署与发布 - → 数据分析师:系统日志、性能数据、AI 调用成本数据 - → 开发:生产问题报告(热修复触发) **生产问题处理流程**: ``` 生产问题发现 → 开发热修复 → 测试快速回归 → DevOps 重新部署 ``` --- ## 经验感知钩子 > 本节由 uto-experience-hook Rule 驱动,此处为提示性说明。 执行本 Skill 过程中,若触发以下任一信号,立即追加一行到暂存区(不中断主任务): - **踩坑**:遇到错误且踩坑速查中找不到解决方案,最终找到了正确做法 - **新发现**:完成了某个当前 Skill 流程未覆盖的步骤,且未来会重复用到 - **步骤偏差**:Skill 描述的步骤顺序/内容与实际执行不符 - **缺失 Skill**:遇到某类任务没有对应 Skill,只能凭经验执行 暂存格式(追加到 .cursor/skills/skill-index/PENDING-EXPERIENCES.md): ` | [今日日期] | [本Skill目录名] | [信号类型] | [一句话描述经验内容] | 🔲 待处理 | ` **所有执行步骤完成后**,检查暂存区是否有新增条目。若有,在收尾时告知用户: 「本次执行感知到 N 条经验(已暂存),任务确认跑通后可说「做一次项目复盘」处理。」 --- ## 变更记录 ### v1.1.2 — 2026-03-19 — 正式注册服务器 AI 接口,明确不需手写 SSH/Paramiko **根因**:AGENT_RULES.md 新增 RULE-25,要求部署验证时优先使用 `/api/internal/chat` 接口;Skill 中虽已有 `ask_server()` 函数,但未明确"不需手写 SSH/Paramiko 脚本",也未链接接口规范文档。 **经验核心**:部署后验证全程使用服务器 AI 接口,无需 SSH/Paramiko;接口规范集中维护于 `_内部总控/开发规范/AI调用服务器助手接口规范.md`。 **修改内容**: - 修改:`## ⚠️ 部署后验证清单` 原则行 → 补充"不需要手写 SSH/Paramiko 脚本"说明及接口规范文档链接 **验证结果**: - 正向验证:下次部署后验证时,DevOps 工程师直接查本节即可找到接口规范 → 待验证 **验证状态**:🔵 待验证 --- ## 经验感知 **本 Skill 执行过程中,自动遵循 `auto-experience-hook` Rule 进行经验暂存。** 部署调试场景下,以下情况**必须触发**(即使正在连续调试,也不跳过): - 遇到任何新的 nginx/docker/network 报错且踩坑速查没有记录 → 信号A(踩坑) - 发现某个变量/配置在 GitHub Secrets 里只能整体替换不能追加 → 信号B(新发现) - 发现某个操作的前提条件(如 git remote 检查、docker network 创建)没有在 Skill 里强制要求 → 信号C(步骤偏差) > 「感知不打断,沉淀在任务后」——每发现一个踩坑,先用一句话暂存,继续调试,任务结束后统一处理。 --- ### v1.1.1 — 2026-03-19 — 追加 Docker 容器访问宿主机 WS 服务踩坑(OPS-08) **根因**:openclaw-proxy 项目实战中,proxy-backend 容器(172.26.0.0/16)通过 host.docker.internal 连接宿主机 WS 服务失败,踩坑速查中无此场景记录。 **经验核心**:容器访问宿主机服务存在三重陷阱——ufw 拦截、服务绑定 loopback、DNS 解析 IP 与实际网段不一致。TCP 连通成功不等于应用层握手成功,必须在代码层验证 WS 握手。 **修改内容**: - 新增:踩坑速查表格 OPS-08 行(Docker 容器→宿主机 WS 服务握手失败,含三重根因和完整修复步骤) **验证结果**: - 正向验证:下次遇到容器访问宿主机服务场景 → 踩坑速查命中 OPS-08,直接给出修复步骤 → 待验证 **验证状态**:🔵 待验证 --- ## 跨项目容器网络架构(tashan-shared 模式) > 适用场景:两个独立 docker compose 项目的容器需要互相访问(如 tashan-world 调用 topiclab-backend 的账号服务)。 ### 何时使用 - 服务A(项目α)需要调用服务B(项目β)的接口 - 两个项目各自有独立的 `docker-compose.yml`,不合并 - 不想通过宿主机端口中转(避免 ufw/绑定地址问题) ### 架构方案:external network ```yaml # 项目α 的 docker-compose.yml networks: tashan-shared: external: true # 声明为外部网络,不自动创建 services: frontend: networks: - default - tashan-shared backend: networks: - default - tashan-shared ``` ```yaml # 项目β 的 docker-compose.yml(同样声明加入) networks: tashan-shared: external: true services: backend: container_name: topiclab-backend # 固定容器名,供跨项目引用 networks: - default - tashan-shared ``` ### 容器名稳定性 跨项目引用的容器必须保证名称可预测,有两种方式: | 方式 | 写法 | 适用场景 | |---|---|---| | 显式固定 | `container_name: topiclab-backend` | 推荐,名称最清晰 | | 默认命名规则 | `{项目目录名}_{服务名}_1` | 未设置 container_name 时使用 | 在调用方配置中直接用容器名作为 hostname: ```nginx # 项目α 的 nginx.conf 中跨项目调用 upstream account_service { server topiclab-backend:8000; # 直接用容器名 } ``` ### deploy.yml 必须的幂等创建步骤 external network 必须在 `docker compose up` 之前存在,否则报错。在 deploy.yml 的 script 段加入: ```yaml script: | # 幂等创建共享网络(已存在则跳过,不报错) docker network create tashan-shared 2>/dev/null || true cd /var/www/github-actions/repos/${{ github.event.repository.name }} git pull origin main printf '%s\n' '${{ secrets.DEPLOY_ENV }}' > .env docker compose down docker compose build --no-cache docker compose up -d ``` > **顺序要求**:`docker network create` 必须在 `docker compose up` 之前执行。两个项目的 deploy.yml 都加这一行,幂等,互不干扰。 ### ⚠️ nginx upstream DNS 解析注意 nginx 在**启动时**解析 upstream 中的域名/容器名。如果被引用的容器(如 topiclab-backend)在 nginx 启动后才加入 tashan-shared 网络,会导致 upstream 解析失败(502)。 处理方式: - 确保被依赖的容器先于调用方 nginx 启动并加入网络 - 或在 nginx 配置中使用变量引用 upstream(延迟解析):`set $backend topiclab-backend:8000; proxy_pass http://$backend;` - 修改了 network 配置后,必须 `docker compose down && docker compose up -d`(reload 不够,需重建容器) --- ## 远程访问:Tailscale 跨机器 SSH > 适用场景:需要通过公网 SSH 访问另一台机器(如 Mac ↔ Windows 跨网),无需开放公网端口。 ### macOS 安装 Tailscale(无 Homebrew 时) ```bash # Step 1:从 JSON API 获取最新 pkg 名(universal-package 字段) curl -fL "https://dl.tailscale.com/stable/?mode=json" | \ python3 -c "import sys,json; d=json.load(sys.stdin); print(d['MacZips']['universal-package'])" # Step 2:下载 pkg(将 [pkg名] 替换为上一步输出的文件名) curl -fL "https://dl.tailscale.com/stable/[pkg名]" -o /tmp/tailscale.pkg # Step 3:安装(弹出系统授权对话框,免命令行 sudo) osascript -e 'do shell script "installer -pkg /tmp/tailscale.pkg -target /" with administrator privileges' # Step 4:启动 open -a Tailscale ``` ### 前置验证:确认两台机器在同一 tailnet 连接前必须先确认双方在同一 tailnet,否则 `ssh` 直接超时且无任何错误提示: ```bash # 查看 peer 列表(目标机必须出现在列表中) /Applications/Tailscale.app/Contents/MacOS/Tailscale status ``` **若只看到自己(no peers)**:两机器登录了不同账号,不在同一 tailnet。 诊断(对比两台机器的 tailnet 域名): ```bash /Applications/Tailscale.app/Contents/MacOS/Tailscale whois [本机Tailscale-IP] # 对比 Name 字段的 tailnet 域名,如 tail1610cb.ts.net vs tail226c2f.ts.net → 不同 = 不同 tailnet ``` 解决方案: - 切换其中一台到另一台的账号重新登录 Tailscale - 或通过 [Tailscale 管理后台](https://admin.tailscale.com) 使用 Share Node 功能共享节点 ### Windows:管理员用户的 SSH 公钥配置 > ⚠️ **管理员用户(Administrators 组成员)的公钥不放 `~/.ssh/authorized_keys`**,必须放以下专用路径: ```powershell # 在 Windows 目标机上,以「管理员身份运行」PowerShell 执行: $pubkey = "ssh-ed25519 AAAA... user@host" Add-Content -Path "C:\ProgramData\ssh\administrators_authorized_keys" -Value $pubkey # 必须设置严格权限(否则 OpenSSH 会静默忽略该文件,SSH 始终要求输入密码) icacls "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" ``` > **根因**:Windows OpenSSH 要求 `administrators_authorized_keys` 只允许 Administrators 和 SYSTEM 访问,继承了其他权限时会静默忽略整个文件。 ### SSH config 别名(本机 Mac) 配置后可直接用 `ssh ` 连接,无需每次输入 IP、用户名、密钥路径: ``` # ~/.ssh/config Host HostName User IdentityFile ~/.ssh/id_ed25519 StrictHostKeyChecking accept-new ``` ### ⚠️ 注意:Tailscale IP 可能变化 同一账号**重新登录 Tailscale** 后 IP 可能变化(如从 `100.126.144.121` 变为 `100.104.223.4`): - 每次建立新连接前先运行 `tailscale status` 确认当前 IP - SSH config 中的 `HostName` 需同步更新(否则连接超时,无明显报错) --- ### v1.3.0 — 2026-03-22 — 新增「Tailscale 跨机器 SSH 远程访问」章节 **根因**:tashan-openbrain 项目跨机器调试时,需要从 Mac 通过 Tailscale SSH 连接 Windows 机器,踩了以下五个坑:① 无 Homebrew 时不知如何安装 Tailscale;② tailnet 不一致导致 SSH 超时无任何报错;③ Windows 管理员用户公钥路径错误(应放 `administrators_authorized_keys`);④ icacls 权限配置缺失导致公钥静默被忽略;⑤ Tailscale IP 重连后变化但 SSH config 未同步。DevOps Skill 原无任何 Tailscale/跨机器 SSH 覆盖。 **经验核心**:Tailscale 建立跨网 SSH 的关键前置条件是「同一 tailnet」;Windows 管理员用户需要单独处理公钥路径和权限;IP 变化是隐性陷阱。 **修改内容**: - 新增:`## 远程访问:Tailscale 跨机器 SSH` 章节(含5个知识点:macOS无Homebrew安装 / tailnet前置验证 / Windows管理员公钥配置 / SSH config别名 / IP变化注意) **验证结果**: - 正向验证:本次任务已按上述流程完整跑通 Mac→Windows Tailscale SSH 连接 ✅ - 负向验证:现有部署流程(Docker/nginx/CI-CD)不受影响 **验证状态**:✅ 已验证(本次任务实战验证) --- ## Windows 本地进程公网化(反向 SSH 隧道模式) > 适用场景:Windows 本地运行前端/后端服务,需通过 `xxx.tashan.chat` 公网访问。 > 核心路径:**Windows → ECS(Tailscale IP)反向 SSH 隧道 → ECS nginx → 用户浏览器** ### 前置条件检查 ``` □ Windows 与 ECS 均在同一 Tailscale tailnet(`tailscale status` 确认) □ ECS 已生成 HTTPS 证书(acme.sh,见下方「证书申请完整流程」) □ ECS nginx 已配置对应子域名 server block □ Windows 运行:backend(port 8001)+ frontend(vite preview,port 3104) ``` ### ⚠️ 密钥类型必须用 RSA(不用 ED25519) **Windows OpenSSH 9.5p2 已知 bug**:使用 ED25519 密钥时,`publickey-hostbound-v00@openssh.com` 扩展在无 `ssh-agent` 的非交互式 SSH 会话中签名静默失败(debug 日志显示 `we did not send a packet, disable method`),导致认证永远失败,即使密钥已在服务器 `authorized_keys` 中。 **解决方案**:生成 RSA 密钥(不触发 hostbound 扩展): ```cmd :: 在 Windows 命令行(MSI 用户 SSH 会话中,非 WMI) del C:\Users\MSI\.ssh\id_ed25519 C:\Users\MSI\.ssh\id_ed25519.pub echo y | C:\WINDOWS\System32\OpenSSH\ssh-keygen.exe -t rsa -b 4096 -f C:\Users\MSI\.ssh\id_ed25519 -P "" type C:\Users\MSI\.ssh\id_ed25519.pub ``` 然后把公钥加入 ECS `~/.ssh/authorized_keys`(通过 OpenClaw API 或 aup 中转)。 ### 前端构建与服务 ```powershell # 构建生产包 cd D:\tashan-openbrain_myagent\frontend npm run build # vite.config.ts 中必须加入 preview.allowedHosts: true,否则外部域名访问被阻 # preview: # allowedHosts: true # host: '0.0.0.0' # port: 3104 # 启动静态文件服务(vite preview) npm run preview -- --host 0.0.0.0 --port 3104 ``` ### 反向 SSH 隧道脚本(D:\start-tunnel.bat) ```bat @echo off :loop C:\WINDOWS\System32\OpenSSH\ssh.exe -N ^ -o StrictHostKeyChecking=accept-new ^ -o ServerAliveInterval=30 ^ -o ServerAliveCountMax=3 ^ -R 13104:127.0.0.1:3104 ^ -R 18104:127.0.0.1:8001 ^ -i C:\Users\MSI\.ssh\id_ed25519 ^ root@ timeout /t 5 /nobreak goto loop ``` > 注:用 ECS **Tailscale IP**(如 `100.116.135.33`),不用公网 IP(Aliyun 安全组会阻断 Windows→ECS 的直连)。 ### 启动持久化进程(从 SSH 会话) 从 SSH 会话启动持久进程必须用 **WMI Win32_Process.Create**,用 `Start-Process` 在无桌面的 SSH session 中无效: ```powershell $wmi = [wmiclass]"Win32_Process" # 后端 $wmi.Create("D:\tashan-openbrain_myagent\.venv\Scripts\python.exe -m uvicorn main:app --host 0.0.0.0 --port 8001", "D:\tashan-openbrain_myagent\backend") # 前端(批处理文件中调用 npm.cmd) $wmi.Create("cmd.exe /c D:\start-fe.bat", "D:\") # 隧道 $wmi.Create("cmd.exe /c D:\start-tunnel.bat", "D:\") ``` ### ECS nginx 配置(sub.tashan.chat) ```nginx server { listen 443 ssl; server_name sub.tashan.chat; ssl_certificate /etc/letsencrypt/live/sub.tashan.chat/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/sub.tashan.chat/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; location /api/ { proxy_pass http://127.0.0.1:18104/; # 反向隧道后端端口 proxy_buffering off; proxy_read_timeout 600s; proxy_http_version 1.1; proxy_set_header Connection ""; } location / { proxy_pass http://127.0.0.1:13104; # 反向隧道前端端口 } } ``` --- ## OpenClaw API:在 ECS 上执行命令 > ECS 上运行的 OpenClaw 服务提供了一个内部 API,AI 可通过此接口在 ECS 执行 shell 命令,无需 SSH 密钥直连。 ```python import httpx def run_on_ecs(command: str) -> str: """通过 OpenClaw 在 ECS(101.200.234.115)执行命令。 适用于:nginx 配置/证书申请/authorized_keys 修改等场景。 不适用于:需要停止 nginx 的命令(nginx 停止后此接口不可达)。 """ r = httpx.post( "https://openclaw.tashan.chat/api/internal/chat", headers={"X-API-Key": "tashan-internal-2026"}, json={"message": command}, timeout=90, ) return r.json()["reply"] # 示例:查看当前 authorized_keys # run_on_ecs("cat ~/.ssh/authorized_keys") # 示例:添加公钥 # run_on_ecs("echo 'ssh-rsa AAAA...' >> ~/.ssh/authorized_keys") # 示例:申请证书(webroot 模式,nginx 不停) # run_on_ecs("mkdir -p /var/www/acme-challenge/.well-known/acme-challenge && ...") # run_on_ecs("~/.acme.sh/acme.sh --issue --webroot /var/www/acme-challenge -d xxx.tashan.chat") ``` **⚠️ 注意**: - `systemctl stop nginx` 后此接口不可达(自身由 nginx 代理),需改用 aup bore 隧道 + SSH 中转 - 证书申请优先用 **webroot 模式**,避免 stop nginx 导致接口断连 - bore.pub 隧道在多个进程并发连接时会过载(MaxStartups 超限),同一时刻只保留 1 条连接 --- ## ECS 接入 Tailscale 后的 UFW 配置 ECS 加入 Tailscale 后,若 UFW 已启用,需要明确放行 Tailscale 接口和子网,否则 Tailscale 直连 SSH 仍被 UFW 阻断: ```bash # 允许所有来自 Tailscale 接口的流量 ufw allow in on tailscale0 # 允许 Tailscale CGNAT 子网(100.64.0.0/10)的 SSH ufw allow from 100.64.0.0/10 to any port 22 comment 'Tailscale SSH' # 重载生效 ufw reload ``` 验证:`/Applications/Tailscale.app/Contents/MacOS/Tailscale ping ` 返回 pong 后,`ssh root@` 应可连通。 ### v1.2.5 — 2026-03-21 — 新增「功能路径测试门」V11+(第三次同类问题修复) **根因**:第三次发生同类问题——「完成任务但没测新功能路径」。本次创建了 20 个 NPC 大脑 + Proxy 对话功能,RULE-LOC V1-V11 全部通过后宣布就绪。用户追问后才测试 Proxy 对话,发现 `run_agent_stream()` 缺少 `system_prompt_override` 参数,导致所有 NPC 的 Proxy 对话 100% 返回 500 错误。RULE-LOC 的根本缺陷:只有固定的通用检查项,没有机制要求「对本次任务新增的功能路径做端到端测试」。 **问题规律(三次复现)**: - 第一次:重启后只做 /health,未跑完整 RULE-LOC → 加了 RULE-LOC - 第二次:创建 NPC 后只看广场 API,未测 Proxy 对话 → 本次修复 - 第三次(预防):功能路径测试门,强制要求「任务改了什么就测什么」 **修改内容**: - 修改:RULE-LOC Step L4 → 在 V10 后新增「V11+ 功能路径测试门」 - 内容:3类场景(账号/内容数据 / 新路由 / SSE流)+ 判断规则 + 「重启必须重跑清单」禁令 **验证结果**:本次任务已验证 V11(广场20个NPC)+ V12(Proxy对话代码修复后测试,20/20) **已知风险**:V11+ 的「本次任务新增了什么」需要 AI 自判断,可能遗漏低可见度变更(如 DB schema 变更对 API 的影响)。下一步可考虑在每次任务开始时明确列出「本次会影响的功能路径」。 --- ### v1.2.4 — 2026-03-21 — V10 数据库认证可用性验证(Phase 2 连通性补丁) **根因**:TASK-20260321-48 本地启动后未配置 DATABASE_URL 和 JWT_SECRET_KEY(.env.example 根本没有这两个变量),导致登录时返回 503「数据库未配置,Phase 2 功能不可用」。RULE-LOC 的 V7 只测了端点「存在性」(HTTP 422),未测「功能可用性」(HTTP 401 vs 503),是验证覆盖不足。 **修改内容**: - 新增:RULE-LOC V10「数据库认证可用性测试」——发送格式正确但凭证错误的请求,期望 401(非 503) - 说明:503 = DB 未连通 / 401 = DB 正常 / 422 = 请求格式错误 **配套修改**: - .env.example 补全 Phase 2 必填项(DATABASE_URL / JWT_SECRET_KEY / ADMIN_WORKSPACE_ID),并加注「否则无法登录」警告 **验证结果**:✅ 本次任务 V10 通过(数据库已连通,登录返回 401) --- ### v1.2.3 — 2026-03-21 — 新增 Step L1-L4「本地 dev 启动验证清单」(系统性漏洞修复) **根因**:TASK-20260321-48(tashan-openbrain 本地启动)完成后,只做了一条 `curl /health` 就说「✅ 任务完成」。原因是 DevOps Skill 只有生产 Docker 验证清单,没有本地 dev 启动的验证步骤,缺少明确约束。用户指出这是规范缺失。 **修改内容**: - 新增:Step 0「场景判断」(本地 dev 启动 vs 生产部署),置于激活流程开头 - 新增:「本地 dev 启动验证清单 RULE-LOC」章节,含 Step L1(准备)/ L2(启动)/ L3(等待)/ L4(9项验证) - 明确禁止:9项未全部 ✅ 前说「服务已就绪」 - 明确必测项:V6 要求每个新路由至少测一次可达性(防止注册失败静默) **验证结果**: - 正向验证:本次任务执行后补做了完整 9 项验证,发现 V6 路由 prefix 冲突(/api/twin→404),修复后全部通过 - 负向验证:生产部署场景走原有 Step 1-5 + 生产验证清单,不触发 Step L **已知风险**:V6「本次新增路由」需要 AI 自己判断哪些是「本次新增」,有遗漏风险——可通过任务日志回顾确认 **验证状态**:✅ 本次任务已验证 --- ### v1.2.2 — 2026-03-19 — 验证6(浏览器UI冒烟)+ 部署后收尾引导(PD-001 Gap 修复) **根因**:① PD-001 沙盘验证发现部署完成后缺乏触发 project-closeout 的机制,收尾链路断裂;② 先前缺少「接口通了但界面卡死」的浏览器层验证。 **修改内容**: - 新增:验证6「浏览器UI冒烟测试」(RULE-32 强化) - 新增:「所有验证通过后的下一步」章节——告知用户可选的收尾步骤(project-closeout + project-retrospective) **验证结果**: - 正向验证:部署完成后,AI 应主动提示「建议执行项目收尾」 - 负向验证:部署中途失败时(验证1-5 不通过),不触发收尾提示 **验证状态**:🔵 待验证 --- ### v1.4.0 — 2026-03-22 — 新增三章节:Windows公网化 + OpenClaw API + ECS Tailscale UFW **根因**:myagent 项目 Windows 本地进程公网化部署全链路实战中,踩了五个高频坑:① Aliyun 安全组阻断 Windows→ECS 直连,必须走 Tailscale;② Windows OpenSSH 9.5p2 ED25519 `publickey-hostbound` 静默失败,只能用 RSA;③ `Start-Process` 在无桌面 SSH 会话中无法持久化进程,必须用 WMI;④ vite preview 默认拒绝外部 Host,须设 `allowedHosts: true`;⑤ ECS Tailscale 接入后 UFW 未放行 tailscale0,Tailscale 直连 SSH 被阻断。此外,通过 OpenClaw API 在 ECS 执行命令(无需 SSH 密钥)是部署中的核心便捷工具,但之前无 Skill 覆盖。 **经验核心**:Windows 公网化的三个关键约束:① Tailscale 绕过安全组;② RSA 密钥绕过 OpenSSH bug;③ WMI 绕过 SSH session 生命周期限制。 **修改内容**: - 新增:`## Windows 本地进程公网化(反向 SSH 隧道模式)` 章节(含 RSA 密钥/vite preview/WMI/nginx 模板) - 新增:`## OpenClaw API:在 ECS 上执行命令` 章节(含 Python 调用模板和使用限制) - 新增:`## ECS 接入 Tailscale 后的 UFW 配置` 章节(含 tailscale0/100.64.0.0/10 规则) - 修改:踩坑速查表 → 新增 OPS-28(Python 3.9 union type)、OPS-29(跨平台.env路径) **验证结果**: - 正向验证:`https://openbrain-win.tashan.chat` 已通过本次链路实测,HTTP 200 ✅ - 负向验证:现有 Docker/CI-CD/生产部署流程不受影响 **验证状态**:✅ 已验证(2026-03-22 myagent Windows 公网化实战) **根因**:tashan-world 调用 topiclab-backend 账号服务时,发现跨 docker compose 项目的容器通信没有对应的 Skill 覆盖,只能凭经验摸索。实战验证后发现 external network 是标准做法,且有多个细节易错(network 须先建、container_name 须固定、nginx DNS 解析时机)。 **经验核心**:跨项目容器通信的正确架构是 docker external network;deploy.yml 必须幂等创建该 network;nginx upstream 的容器名解析在启动时发生,容器必须先入网。 **修改内容**: - 新增:`## 跨项目容器网络架构(tashan-shared 模式)` 章节(含 external network 配置模板、container_name 稳定性说明、deploy.yml 幂等创建命令、nginx DNS 解析注意事项) **验证结果**: - 正向验证:下次处理跨项目容器通信场景时,本章节能直接给出可复用的配置模板 → 待验证 - 负向验证:单项目内部通信不触发本章节,不影响现有部署流程 **验证状态**:🔵 待验证 --- ### v1.3.1 — 2026-03-23 — 知识导航表新增 D0 认知根行 **根因**:role-DevOps 导航表缺少 D0 认知根确认行,AI 执行时没有「先确认当前生产环境状态」的强制步骤(连接缺口)。 **修改内容**: - 新增:知识导航表 D0 行 → 指向「部署架构总览.md」,先确认生产环境状态再执行部署 **验证状态**:🔵 待验证 **备份路径**:`history/SKILL_v1.3.0_20260323_before_nav.md`