Files
HarborForge.Backend/docs/OPENCLAW_PLUGIN_DEV_PLAN.md
zhi 929a722c66 docs: add OpenClaw Plugin development plan
- docs/OPENCLAW_PLUGIN_DEV_PLAN.md: Complete development plan
  * Backend capability assessment
  * Security analysis (current HTTP heartbeat lacks validation)
  * Three implementation options (enhanced HTTP / API Key / encrypted payload)
  * Phased development plan (Phase 1-3)
  * API specifications
  * Data models
  * Sequence diagrams

- docs/examples/monitor_heartbeat_secure.py: Reference implementation
  for secure HTTP heartbeat with challenge validation
2026-03-19 14:19:46 +00:00

15 KiB
Raw Blame History

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 问题 🔴

# 当前实现 (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 实现

# 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 验证:

@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 方案 BAPI Key 模式(推荐长期方案)

添加长期有效的 API Key

# 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

@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

Response:
  public_key_pem: string  # RSA 公钥 (PEM 格式)
  key_id: string         # 公钥指纹

POST /admin/servers

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

连接流程:
  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当前版本不安全

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增强版

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 模型

# 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 配置模型

// ~/.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 首次部署流程

sequenceDiagram
    participant Admin
    participant Backend
    participant Plugin
    participant Server as Server State

    Admin->>Backend: POST /admin/servers<br/>{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<br/>{challenge_uuid, ...}
            Backend->>Plugin: {ok: true}
        end
    end

7.2 数据上报格式

{
  "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
更新频率: 随开发进度更新