# 设备端远程管理接口清单表（V1：对外 rk3588-agent；对内 media-server）

> 目标：供管理端（Go）与设备端联调对照。
>
> 约定：
> - 对外：`rk3588-agent` HTTP 端口默认 `9100`（可配置），UDP 发现端口默认 `35688`。
> - 对内：`media-server` 继续提供现有 `/api/*`（默认 9000），由 agent 本机调用。

---

## 0. 通用规范

### 0.1 基础 URL
- Agent Base: `http://<device_ip>:<agent_port>`
- Media-server Base（仅 agent 使用）: `http://127.0.0.1:<media_port>`

### 0.2 鉴权（agent 对外）
- Header：`X-RK-Token: <token>`
- **必须鉴权**：所有写接口（PUT/POST 会改变设备状态或写盘）
- **读接口**：默认可不鉴权；若 `agent.require_token_for_read=true` 则也必须鉴权

### 0.3 统一响应格式
- 成功（通用）：`{"ok":true}`
- 失败（通用）：`{"error":"<message>"}`（与现有 `ErrorJson()` 风格一致）

### 0.4 统一错误码/HTTP 状态（agent 对外）
agent 不要求返回结构化 error_code（保持 `{"error":...}`），但**要求 HTTP 状态码语义稳定**：

| 场景 | HTTP | error.message 示例 |
|---|---:|---|
| 未鉴权/Token 错误/缺失 | 401 | `unauthorized` |
| JSON 解析失败 / 字段缺失 / 校验失败 | 400 | `invalid json: ...` / `validation failed: ...` |
| 资源不存在 | 404 | `not found` |
| 方法不允许 | 405 | `method not allowed` |
| 冲突（如 node_id 不唯一等） | 409 | `... not unique ...` |
| 写盘失败/内部异常/reload 崩溃性失败 | 500 | `internal error: ...` |
| 上传过大 | 413（推荐）或 400 | `payload too large` |

### 0.5 端口与协议
- UDP 发现：`35688/udp`（可配置）
- Agent HTTP：`9100/tcp`（默认，可配置）
- Media-server HTTP：`9000/tcp`（默认，可配置）

---

## 1. UDP 发现协议（Option A）

### 1.1 Discover 请求（manager → broadcast）
**发送目标**：同网段广播地址 `255.255.255.255:35688` 或各网卡广播地址

**数据格式**：两行 UTF-8 文本

Line1（固定魔法串）：
```
RK3588SYS_DISCOVERY_V1
```

Line2（JSON）：
```json
{"type":"discover","req_id":"<uuid>","reply_port":0}
```

字段：
- `type`: 固定 `discover`
- `req_id`: 管理端生成 UUID，用于匹配回复
- `reply_port`: 保留字段（V1 可固定 0）

### 1.2 Discover 回复（device → manager 单播）
**发送目标**：请求报文 source ip:source port

两行文本：

Line1：
```
RK3588SYS_DISCOVERY_V1
```

Line2（JSON）：
```json
{
  "type":"discover_reply",
  "req_id":"<uuid>",
  "device_id":"rk3588-...",
  "device_name":"rk3588-cam-01",
  "hostname":"rk3588",
  "ip":"10.0.0.21",
  "agent_port":9100,
  "media_port":9000,
  "version":"0.0.0-dev",
  "git_sha":"e5894c2",
  "uptime_sec":12345
}
```

字段说明：
- `device_id`: 稳定唯一（优先 `/etc/machine-id`，否则 MAC/序列号；最后 fallback 生成并落盘）
- `device_name`: 可配置的人类可读名称（agent 配置 `device_name`）
- `agent_port`: agent HTTP 端口
- `media_port`: media-server HTTP 端口（用于调试/展示；管理端可不直连）

---

## 2. 设备信息（agent 对外）

### 2.1 `GET /v1/info`
用途：设备列表/详情页展示、联调确认端口/版本/配置路径。

**Auth**：读接口（见 0.2）

Response 200：
```json
{
  "device_id":"rk3588-...",
  "device_name":"rk3588-cam-01",
  "hostname":"rk3588",
  "ip":"10.0.0.21",
  "agent_port":9100,
  "media_port":9000,
  "version":"0.0.0-dev",
  "git_sha":"e5894c2",
  "config_path":"/etc/rk3588sys/config.json",
  "last_good_path":"/etc/rk3588sys/config.json.last_good.json",
  "uptime_sec":12345
}
```

失败：401/500 + `{"error":"..."}`

---

## 3. 配置下发（agent 对外）

### 3.1 `PUT /v1/config`
用途：管理端上传完整 root config（可含 templates/instances），agent 原子写盘后触发 `media-server` reload。

**Auth**：必须（401）

Headers：
- `Content-Type: application/json`
- `X-RK-Token: ...`

Body：root config JSON

agent 处理步骤（必须满足）：
1) 解析 JSON（语法有效即可；语义校验交由 media-server reload）
2) 原子写入 `config_path`
3) 调用 media-server：`POST /api/config/reload`
4) 若 reload 失败：调用 media-server：`POST /api/config/rollback`，并返回 500（包含 reload/rollback 错误信息）

Response 200：
```json
{"ok":true}
```

失败：
- 400：JSON 解析失败
- 500：写盘失败 / reload 失败（已尝试 rollback）

---

## 4. 模型管理（agent 对外）

### 4.1 `PUT /v1/models/{name}`
用途：上传模型文件并落盘，维护 manifest，返回可引用的 `path`。

**Auth**：必须（401）

Path params：
- `name`: string（建议仅允许 `[A-Za-z0-9._-]`，超出则 400）

Headers：
- `Content-Type: application/octet-stream`
- `Content-Length: <n>`（必须）
- `X-RK-Token: ...`
- `X-Model-Sha256: <hex>`（可选；若存在必须匹配实际 sha256，否则 400）

Body：二进制文件（建议限制扩展名白名单 `.rknn`；V1 可由 name 或内容类型控制）

Response 200：
```json
{
  "ok": true,
  "name": "yolov5s-640",
  "sha256": "...",
  "path": "/opt/rk3588sys/models/files/yolov5s-640__abcd.rknn",
  "size": 12345678
}
```

失败：
- 400：缺 Content-Length / name 非法 / sha256 不匹配
- 413（推荐）或 400：超过 `max_upload_mb`
- 500：写盘失败/manifest 更新失败

### 4.2 `GET /v1/models`
用途：列出设备端已有模型。

**Auth**：读接口（见 0.2）

Response 200：
```json
{
  "items": [
    {
      "name": "yolov5s-640",
      "sha256": "...",
      "path": "/opt/rk3588sys/models/files/...",
      "size": 123,
      "mtime_ms": 1730000000000
    }
  ]
}
```

失败：500 + `{"error":"..."}`

---

## 5. 业务图热更新/回滚（agent 对外）

### 5.1 `POST /v1/media-server/reload`
用途：触发本机 media-server `POST /api/config/reload`。

**Auth**：必须（401）

Response 200：`{"ok":true}`

失败：500 + `{"error":"..."}`

### 5.2 `POST /v1/media-server/rollback`
用途：触发本机 media-server `POST /api/config/rollback`。

**Auth**：必须（401）

Response 200：`{"ok":true}`

失败：500 + `{"error":"..."}`

### 5.3 `PUT /v1/media-server/configs/{name}`
用途：上传 media-server 配置文件到 `agent.media_server_process.configs_dir`。

批量上传：对不同文件名重复调用该接口即可。

**Auth**：必须（401）

Headers：
- `Content-Type: application/json`
- `X-RK-Token: ...`

Path params：
- `name`: string（仅允许 `[A-Za-z0-9._-]`，禁止 `/`、`\\`、`..`；若无 `.json` 后缀则自动追加）

Body：media-server 配置 JSON

Response 200：
```json
{
  "ok": true,
  "name": "cam1.json",
  "path": "/opt/rk3588sys/configs/cam1.json",
  "size": 1234,
  "mtime_ms": 1730000000000
}
```

失败：
- 400：name 非法 / Content-Type 非 application/json / JSON 无效 / 空 body
- 401：unauthorized
- 413：超过 `max_upload_mb`
- 501：`configs_dir` 未配置
- 500：写盘失败

## 6. 主程序进程控制（agent 对外）

> 说明：该能力用于“启动/重启/关闭主程序（media-server）并选择加载哪个配置文件”。
>
> agent 启动 media-server 时会显式传入：`--config <resolved_config_path>`，因此不依赖 media-server 内部默认配置。
>
> 路径说明：
> - `agent.media_server_process` 下的 `exec_path/work_dir/configs_dir/pid_file` 支持绝对或相对路径；若为相对路径，则以 agent 启动参数 `--config <agent_config_path>` 的**配置文件所在目录**为基准解析。
> - `agent.config_path` 建议使用**绝对路径**；若使用相对路径：
>   - `PUT /v1/config` 写盘时，以 **agent 进程当前工作目录（CWD）** 为基准；
>   - 通过 `/v1/media-server/start|restart`（缺省 config）启动时，会把该相对路径原样传给 media-server，最终以 **media_server_process.work_dir** 为基准解析。

### 6.1 `POST /v1/media-server/start`
用途：启动本机 media-server（若已运行则幂等返回 ok；若已运行但 config 不同则 409）。

**Auth**：必须（401）

Body（可选，JSON）：
```json
{"config":"cam1"}
```

说明：若请求 body 非空，则必须带 `Content-Type: application/json`；若 body 为空，可不带该 header。

`config` 解析规则：
- 为空/缺省：使用 `agent.config_path`
- 非空：只允许文件名/配置名（禁止包含 `/`、`\\`、`..`）；若不带扩展名自动补 `.json`；最终从 `agent.media_server_process.configs_dir` 下解析为 `<configs_dir>/<config>.json`

Response 200：
```json
{"ok":true,"running":true,"pid":1234,"config_path":"/etc/rk3588sys/config.json"}
```

失败：
- 400：config 不合法 / config 文件不存在
- 409：已运行但 config 不同（提示使用 restart）
- 501：未启用进程控制（agent 配置 `agent.media_server_process.enable=false` 或未配置）
- 500：启动失败 / 写 pidfile 失败

### 6.2 `POST /v1/media-server/restart`
用途：重启本机 media-server（可切换 config）。

**Auth**：必须（401）

Body（可选，JSON）：
```json
{"config":"cam1"}
```

说明：若请求 body 非空，则必须带 `Content-Type: application/json`；若 body 为空，可不带该 header。

Response 200：
```json
{"ok":true,"running":true,"pid":1234,"config_path":"/home/orangepi/Desktop/OrangePi3588Media/configs/cam1.json"}
```

失败：
- 400：config 不合法 / config 文件不存在
- 501：未启用进程控制
- 500：停止/启动失败

### 6.3 `POST /v1/media-server/stop`
用途：停止本机 media-server（未运行也返回 ok）。

**Auth**：必须（401）

Response 200：
```json
{"ok":true,"running":false,"pid":1234,"config_path":"/etc/rk3588sys/config.json"}
```

失败：
- 501：未启用进程控制
- 500：停止失败

### 6.4 `GET /v1/media-server/status`
用途：查询本机 media-server 是否在运行（以 agent 的 pidfile 记录为准）。

**Auth**：读接口（见 0.2）

Response 200：
```json
{"ok":true,"running":true,"pid":1234,"config_path":"/etc/rk3588sys/config.json"}
```

失败：
- 501：未启用进程控制
- 500：读取 pidfile/进程存活检测失败

## 7. 只读代理接口（agent 对外，推荐管理端统一走 agent）

### 7.1 `GET /v1/graphs`
代理 media-server：`GET /api/graphs`

### 7.2 `GET /v1/graphs/{name}`
代理 media-server：`GET /api/graphs/{name}`

### 7.3 `GET /v1/logs/recent?limit=200`
代理 media-server：`GET /api/logs/recent?limit=...`
