# OpenClaw Plugin 开发计划 **文档版本**: 0.1.0 **日期**: 2026-03-19 **状态**: 开发中 --- ## 1. 概述 本文档定义 HarborForge.OpenclawPlugin 的开发计划,以及 Backend 需要提供的接口支持。 ### 1.1 目标 开发一个 OpenClaw 插件,将服务器遥测数据(系统指标 + OpenClaw 状态)实时传输到 HarborForge Monitor。 ### 1.2 架构关系 ``` ┌─────────────────────────────────────────────────────────────┐ │ 远程服务器 (VPS) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ OpenClaw Gateway │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ │ │ HarborForge.OpenclawPlugin │ │ │ │ │ │ - 生命周期管理 (随 Gateway 启动/停止) │ │ │ │ │ │ - 启动 sidecar 进程 │ │ │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ 启动/管理 │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ │ │ Sidecar (独立 Node 进程) │ │ │ │ │ │ - 收集系统指标 (CPU/内存/磁盘) │ │ │ │ │ │ - 读取 OpenClaw 状态 (agents) │ │ │ │ │ │ - HTTP/WebSocket 上报到 Monitor │ │ │ │ │ └────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ HTTP / WebSocket ▼ ┌─────────────────────────┐ │ HarborForge.Backend │ │ - /monitor/* 接口 │ │ - 数据存储 │ └─────────────────────────┘ ``` --- ## 2. Backend 当前能力评估 ### 2.1 已实现接口 ✅ | 接口 | 功能 | 完整度 | 说明 | |------|------|--------|------| | `GET /monitor/public/server-public-key` | 获取 RSA 公钥 | ✅ 100% | 用于插件加密 | | `POST /admin/servers` | 注册服务器 | ✅ 100% | 返回 server_id | | `POST /admin/servers/{id}/challenge` | 生成 challenge | ✅ 100% | 10分钟有效期 | | `WS /monitor/server/ws` | WebSocket 连接 | ✅ 100% | 完整验证逻辑 | | `POST /monitor/server/heartbeat` | HTTP 心跳 | ⚠️ 50% | 缺少安全验证 | ### 2.2 当前 HTTP Heartbeat 问题 🔴 ```python # 当前实现 (app/api/routers/monitor.py:191-207) @router.post('/server/heartbeat') def server_heartbeat(payload: ServerHeartbeat, db: Session = Depends(get_db)): server = db.query(MonitoredServer).filter( MonitoredServer.identifier == payload.identifier ).first() # 问题:只验证 identifier 存在,不验证 challenge! # 任何人知道 identifier 就可以伪造数据 ``` **对比 WebSocket 实现**: ```python # WebSocket 有完整验证 ch = db.query(ServerChallenge).filter( ServerChallenge.challenge_uuid == challenge_uuid, ServerChallenge.server_id == server.id ).first() if not ch or ch.used_at is not None or ch.expires_at < now(): await websocket.close(code=4401) # 验证失败 ``` --- ## 3. Backend 需要补充的接口 ### 3.1 方案 A:增强 HTTP Heartbeat(推荐短期方案) 添加 challenge_uuid 验证: ```python @router.post('/server/heartbeat') def server_heartbeat( payload: ServerHeartbeatSecure, # 包含 challenge_uuid db: Session = Depends(get_db) ): # 1. 验证服务器 server = db.query(MonitoredServer).filter(...).first() if not server: raise HTTPException(404, 'unknown server') # 2. 验证 challenge ch = db.query(ServerChallenge).filter( ServerChallenge.challenge_uuid == payload.challenge_uuid, ServerChallenge.server_id == server.id ).first() if not ch or ch.expires_at < now(): raise HTTPException(401, 'invalid or expired challenge') # 3. 存储数据... ``` **优点**: 与现有 WebSocket 验证逻辑一致 **缺点**: Challenge 10分钟过期,需要定期重新注册 ### 3.2 方案 B:API Key 模式(推荐长期方案) 添加长期有效的 API Key: ```python # 1. 模型添加 api_key 字段 class MonitoredServer(Base): ... api_key = Column(String(64), nullable=True, unique=True, index=True) # 2. 新增接口:生成/重置 API Key @router.post('/admin/servers/{id}/api-key') def generate_api_key(server_id: int, ...): api_key = secrets.token_urlsafe(32) # 存储并返回 (仅显示一次) # 3. 心跳接口验证 API Key @router.post('/server/heartbeat-v2') def server_heartbeat_v2( payload: ServerHeartbeat, x_api_key: str = Header(...), db: Session = Depends(get_db) ): server = db.query(MonitoredServer).filter( MonitoredServer.identifier == payload.identifier, MonitoredServer.api_key == x_api_key ).first() if not server: raise HTTPException(401, 'invalid credentials') ``` **优点**: 长期有效,适合自动化 Agent **缺点**: 需要新增数据库字段和接口 ### 3.3 方案 C:加密 Payload(最高安全) 参考 WebSocket 的 encrypted_payload: ```python @router.post('/server/heartbeat') def server_heartbeat( encrypted_payload: str = Body(...), # RSA-OAEP 加密 db: Session = Depends(get_db) ): # 1. 解密 data = decrypt_payload_b64(encrypted_payload) # 2. 验证时间戳 (防重放) if not ts_within(data['ts'], max_minutes=10): raise HTTPException(401, 'expired') # 3. 验证 challenge ch = db.query(ServerChallenge).filter( challenge_uuid=data['challenge_uuid'] ).first() ... ``` **优点**: 最高安全性 **缺点**: 客户端实现复杂,需要 RSA 加密 --- ## 4. OpenclawPlugin 开发计划 ### Phase 1: 基础功能开发(2-3天) **目标**: 可运行的基础版本(开发环境) | 任务 | 说明 | 依赖 | |------|------|------| | 1.1 Sidecar 基础架构 | Node.js 项目结构,配置加载 | 无 | | 1.2 系统指标收集 | CPU/内存/磁盘/运行时间 | 无 | | 1.3 OpenClaw 状态读取 | 读取 agents.json,版本信息 | 无 | | 1.4 HTTP 心跳上报 | 使用当前 /heartbeat 接口 | ⚠️ 不安全,仅开发 | | 1.5 Plugin 生命周期 | 随 Gateway 启动/停止 Sidecar | 无 | **验收标准**: - [ ] 可以收集系统指标 - [ ] 可以上报到 Backend - [ ] 可以在 Monitor 面板看到数据 ### Phase 2: 安全增强(2-3天) **目标**: 生产环境可用的安全版本 | 任务 | 说明 | 依赖 | |------|------|------| | 2.1 WebSocket 支持 | 实现 WS 连接和加密握手 | Backend WS 接口 ✅ | | 2.2 或:等待 HTTP 增强 | Backend 添加 challenge 验证 | Backend 更新 | | 2.3 重试/退避逻辑 | 连接失败时指数退避 | 无 | | 2.4 离线缓存 | 暂时存储,恢复后批量上报 | 无 | **验收标准**: - [ ] 连接需要验证(WebSocket 或增强 HTTP) - [ ] 网络中断后自动恢复 - [ ] 数据不丢失 ### Phase 3: 生产就绪(1-2天) **目标**: 稳定可靠的监控系统 | 任务 | 说明 | 依赖 | |------|------|------| | 3.1 日志和诊断 | 结构化日志,调试接口 | 无 | | 3.2 性能优化 | 减少资源占用 | 无 | | 3.3 安装脚本完善 | 参考 PaddedCell 格式 | 无 | | 3.4 文档编写 | 部署指南,故障排查 | 无 | **验收标准**: - [ ] 长时间稳定运行(7天+) - [ ] 资源占用 < 1% CPU,< 50MB 内存 - [ ] 安装脚本一键部署 --- ## 5. 接口规格详细定义 ### 5.1 当前可用接口 #### GET /monitor/public/server-public-key ```yaml Response: public_key_pem: string # RSA 公钥 (PEM 格式) key_id: string # 公钥指纹 ``` #### POST /admin/servers ```yaml Headers: Authorization: Bearer {admin_token} Body: identifier: string # 唯一标识 (如 "vps.t1") display_name: string # 显示名称 Response: id: int identifier: string challenge_uuid: string # 10分钟有效 expires_at: ISO8601 ``` #### WS /monitor/server/ws ```yaml 连接流程: 1. Client -> Server: GET /monitor/server/ws (Upgrade) 2. Client -> Server: { "encrypted_payload": "base64_rsa_encrypted_json" } # 或明文(向后兼容): # { # "identifier": "vps.t1", # "challenge_uuid": "...", # "nonce": "...", # "ts": "2026-03-19T14:00:00Z" # } 3. Server -> Client: { "ok": true, "server_id": 1 } 4. Client -> Server: { "event": "server.metrics", "payload": { "cpu_pct": 12.5, "mem_pct": 41.2, ... } } ``` #### POST /monitor/server/heartbeat(当前版本,不安全) ```yaml Body: identifier: string openclaw_version: string agents: [{id, name, status}] cpu_pct: float mem_pct: float disk_pct: float swap_pct: float Response: ok: true server_id: int last_seen_at: ISO8601 ``` ### 5.2 建议新增接口 #### POST /server/heartbeat-secure(增强版) ```yaml Body: identifier: string challenge_uuid: string # 新增:必填 openclaw_version: string agents: [...] cpu_pct: float mem_pct: float disk_pct: float swap_pct: float timestamp: ISO8601 # 可选:防重放 Response: ok: true server_id: int last_seen_at: ISO8601 challenge_expires_at: ISO8601 Error: 401: { detail: "invalid or expired challenge" } ``` --- ## 6. 数据模型 ### 6.1 当前 Backend 模型 ```python # app/models/monitor.py class MonitoredServer: id: int identifier: str # 唯一标识 display_name: str is_enabled: bool created_by: int created_at: datetime # 建议添加: # api_key: str # 长期有效的 API Key class ServerChallenge: id: int server_id: int challenge_uuid: str # 10分钟有效 expires_at: datetime used_at: datetime # 首次使用时间 created_at: datetime class ServerState: id: int server_id: int openclaw_version: str agents_json: str # JSON 序列化 cpu_pct: float mem_pct: float disk_pct: float swap_pct: float last_seen_at: datetime updated_at: datetime ``` ### 6.2 Plugin 配置模型 ```typescript // ~/.openclaw/openclaw.json { "plugins": { "harborforge-monitor": { "enabled": true, "backendUrl": "https://monitor.hangman-lab.top", "identifier": "vps.t1", // 服务器标识 "challengeUuid": "uuid-here", // 从 /admin/servers/{id}/challenge 获取 "apiKey": "key-here", // 如果使用 API Key 模式(可选) "reportIntervalSec": 30, "httpFallbackIntervalSec": 60, "logLevel": "info" } } } ``` --- ## 7. 开发时序图 ### 7.1 首次部署流程 ```mermaid sequenceDiagram participant Admin participant Backend participant Plugin participant Server as Server State Admin->>Backend: POST /admin/servers
{identifier: "vps.t1"} Backend->>Admin: {id: 1, identifier: "vps.t1"} Admin->>Backend: POST /admin/servers/1/challenge Backend->>Admin: {challenge_uuid: "abc-123", expires_at: "..."} Admin->>Server: 配置 challenge_uuid Note over Server: ~/.openclaw/openclaw.json Server->>Backend: openclaw gateway restart Plugin->>Backend: GET /monitor/public/server-public-key Backend->>Plugin: {public_key_pem: "..."} alt WebSocket 模式 Plugin->>Backend: WS /monitor/server/ws Plugin->>Backend: {challenge_uuid, nonce, ts} Backend->>Plugin: {ok: true} loop 每 30 秒 Plugin->>Backend: {event: "server.metrics", payload: {...}} end else HTTP 模式 loop 每 30 秒 Plugin->>Backend: POST /server/heartbeat
{challenge_uuid, ...} Backend->>Plugin: {ok: true} end end ``` ### 7.2 数据上报格式 ```json { "identifier": "vps.t1", "challenge_uuid": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-03-19T14:30:00Z", "cpu_pct": 12.5, "mem_pct": 41.2, "mem_used_mb": 4096, "mem_total_mb": 8192, "disk_pct": 62.0, "disk_used_gb": 500.5, "disk_total_gb": 1000.0, "swap_pct": 0.0, "uptime_sec": 86400, "load_avg_1m": 0.5, "platform": "linux", "hostname": "vps.t1", "openclaw_version": "1.2.3", "openclaw_agent_count": 2, "openclaw_agents": [ {"id": "dev", "name": "Developer", "status": "running"}, {"id": "ops", "name": "Operator", "status": "idle"} ] } ``` --- ## 8. 风险与缓解 | 风险 | 影响 | 缓解措施 | |------|------|----------| | HTTP Heartbeat 无验证 | 数据伪造 | 使用 WebSocket 或等待 Backend 修复 | | Challenge 10分钟过期 | 需要频繁更新 | Backend 添加 API Key 模式 | | 网络中断 | 数据丢失 | Plugin 实现离线缓存 | | 资源占用过高 | 影响业务 | 控制采集频率,优化实现 | | Sidecar 崩溃 | 监控中断 | Plugin 自动重启 Sidecar | --- ## 9. 下一步行动 ### Backend 团队 - [ ] 决定采用方案 A/B/C 增强 HTTP Heartbeat 安全 - [ ] 实现 `/server/heartbeat-secure` 或增强现有接口 - [ ] (可选)添加 API Key 支持 ### Plugin 开发团队 - [ ] Phase 1: 基础功能开发(使用当前不安全 HTTP,仅开发测试) - [ ] Phase 2: 集成 WebSocket(立即可用,最安全) - [ ] 等待 Backend 更新后,切换到安全 HTTP --- ## 10. 参考文档 - 原始设计文档: `docs/monitor-server-connector-plan.md` - Backend 代码: `app/api/routers/monitor.py` - Backend 模型: `app/models/monitor.py` - 加密服务: `app/services/crypto_box.py` - PaddedCell 安装脚本参考: `https://git.hangman-lab.top/nav/PaddedCell` --- **文档维护者**: HarborForge Team **更新频率**: 随开发进度更新