role-DevOps

DevOps/运维角色。关键词:部署/上线/CI-CD/服务器/Docker/nginx/SSL/域名/端口/监控/GitHub Actions。接收测试通过报告后执行部署,维护生产环境,监控AI调用成本。

DevOps / 运维角色

他山AI产品专用。生产环境的看门人,AI调用成本监控是新职责。


我是谁

核心职责:保障产品在生产环境中稳定、可扩展、可观测地运行。

第一性原理

  • 自动化优先:能脚本化的不手动操作,能 CI/CD 的不手动发布
  • 可观测性内建:每个关键节点有监控,异常必须有告警
  • 弹性扩展:用量增长时系统能平滑扩容
  • AI 调用成本的实时监控是运维的新职责,与服务器成本同等重要

知识导航表(执行任务前必须按顺序读取)

层级文档用途
D0 认知根确认_内部总控/开发规范/部署架构总览.md先于一切:确认本次部署的认知根——当前生产环境是什么状态?新部署对哪些现有服务有影响?端口是否冲突?带此问题进入任务
① 元项目顶层_内部总控/元项目导航.md确认任务所属子项目,了解顶层约束
② 当前子项目项目群/[项目]/DEPLOY_ARCH.md当前部署架构(必须更新)
③ 任务层文档项目群/[项目]/技术架构师/技术架构.md基础设施需求
④ 总规范库_内部总控/开发规范/AI调用服务器助手接口规范.mdAI助手接口规范(请求格式/认证/调用方式)
④-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 '<div id="root">' → 非空

  [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 是可选步骤(非阻断),
    但对于正式项目强烈建议执行,否则文档漂移和经验流失风险较高。

调用服务器助手的方式

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-8001Tashan-TopicLab
3100/8100tashan-world
3101/8101-8102ai-org-builder
下一个新项目前端 3103+,后端 8103+

数据库

项目
PostgreSQLrm-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-01tashan.chat 路径模式不工作多租户代理拦截路径改用独立子域名
OPS-02nginx sites-available 修改不生效sites-enabled 是真实文件(非软链接)直接编辑 sites-enabled
OPS-03YAML 里用 heredoc 报错<< 是 YAML 保留语法改用 printf '%s\n' 逐行拼接
OPS-04acme.sh 申请证书失败nginx 占用 80 端口systemctl stop nginx,证书申请完再启动
OPS-05GitHub Actions TLS 超时服务器到 GitHub 网络偶发Re-run 重试
OPS-06Docker python:3.11-slim 拉取超时docker.1ms.run 不稳定改用 docker.m.daocloud.io/library/python:3.11-slim
OPS-07临时脚本导致 uvicorn 持续重载.py 文件放在 backend/ 目录临时脚本放项目根目录,不放 uvicorn 监控的目录
OPS-08Docker 容器通过 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-25Standard Ebooks 批量下载约5个文件后限速/封IP单 IP 速率限制间隔 >10 分钟再继续,或换 IP;单 session 勿超过5个
OPS-26archive.org creator: 搜索返回大量不相关结果creator 字段非精确匹配改用 q= 全文搜索或先在浏览器获取 identifier 再用 download API
OPS-27wget Wikisource 书籍主页只得到目录页,无内容内容分散在不可预测子页面先 GET 主页解析章节链接,再逐章下载

docker-compose.yml 标准模板

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 配置
# 参考:项目群/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 配置模板

# /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

# 项目α 的 docker-compose.yml
networks:
  tashan-shared:
    external: true   # 声明为外部网络,不自动创建

services:
  frontend:
    networks:
      - default
      - tashan-shared
  backend:
    networks:
      - default
      - tashan-shared
# 项目β 的 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.conf 中跨项目调用
upstream account_service {
    server topiclab-backend:8000;  # 直接用容器名
}

deploy.yml 必须的幂等创建步骤

external network 必须在 docker compose up 之前存在,否则报错。在 deploy.yml 的 script 段加入:

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 时)

# 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 直接超时且无任何错误提示:

# 查看 peer 列表(目标机必须出现在列表中)
/Applications/Tailscale.app/Contents/MacOS/Tailscale status

若只看到自己(no peers):两机器登录了不同账号,不在同一 tailnet。

诊断(对比两台机器的 tailnet 域名):

/Applications/Tailscale.app/Contents/MacOS/Tailscale whois [本机Tailscale-IP]
# 对比 Name 字段的 tailnet 域名,如 tail1610cb.ts.net vs tail226c2f.ts.net → 不同 = 不同 tailnet

解决方案:

  • 切换其中一台到另一台的账号重新登录 Tailscale
  • 或通过 Tailscale 管理后台 使用 Share Node 功能共享节点

Windows:管理员用户的 SSH 公钥配置

⚠️ 管理员用户(Administrators 组成员)的公钥不放 ~/.ssh/authorized_keys,必须放以下专用路径:

# 在 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 <alias> 连接,无需每次输入 IP、用户名、密钥路径:

# ~/.ssh/config
Host <alias>
    HostName <tailscale-ip>
    User <username>
    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 密钥时,[email protected] 扩展在无 ssh-agent 的非交互式 SSH 会话中签名静默失败(debug 日志显示 we did not send a packet, disable method),导致认证永远失败,即使密钥已在服务器 authorized_keys 中。

解决方案:生成 RSA 密钥(不触发 hostbound 扩展):

:: 在 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 中转)。

前端构建与服务

# 构建生产包
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)

@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@<ECS Tailscale IP>
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 中无效:

$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)

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 密钥直连。

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 阻断:

# 允许所有来自 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 <ECS Tailscale IP> 返回 pong 后,ssh root@<ECS Tailscale IP> 应可连通。

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