Files
planet/docs/technical/zh/backend-system-service-control.md
2026-04-29 17:27:44 +08:00

334 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 系统服务控制
本文定义后台控制面动作与现有 `planet.sh` 服务管理命令之间的固定映射。
目标是在复用当前运维脚本语义的同时,不向前端或 API 调用方暴露任意 shell 执行能力。
## 范围
- 这套映射只用于管理端运维控制。
- 控制面必须提交固定 action 名称,而不是原始 shell 命令。
- 后端负责把允许的 action 翻译成固定的 `planet.sh` 调用。
## 设计规则
- 只允许执行白名单 action。
- 前端绝不能发送任意 shell 字符串。
- 后端必须从固定映射表构造命令参数。
- 高风险 action 应限制为 `super_admin`
- 在 UI 连续性重要时,优先局部重启,而不是全栈重启。
## Action 映射
| Action 名称 | 用途 | `planet.sh` 命令 | 备注 |
| --- | --- | --- | --- |
| `restart-backend` | 只重启后端 API | `./planet.sh restart -b` | 页面通常短暂失联后由 `/health` 轮询恢复。 |
| `restart-frontend` | 只重启前端开发服务器 | `./planet.sh restart -f` | 页面入口会短暂不可用UI 通过前端入口探测恢复后刷新。 |
| `restart-database` | 重启 PostgreSQL 和 Redis 容器 | `./planet.sh restart -d` | 适合数据库/缓存需要受控重启但不希望重启 UI 的场景。 |
| `restart-system` | 重启整个应用栈 | `./planet.sh restart` | 前端会短暂中断UI 应进入引导恢复模式。 |
| `restart-backend-port` | 在指定端口重启后端 | `./planet.sh restart -b <port>` | 执行前必须由后端校验端口。 |
| `restart-frontend-port` | 在指定端口重启前端 | `./planet.sh restart -f <port>` | 执行前必须由后端校验端口。 |
| `health-check` | 读取当前服务健康状态 | `./planet.sh health` | 安全的只读运维动作。 |
| `show-logs-backend` | 查看后端日志 | `./planet.sh log -b` | 更适合 CLI/运维工具,不建议作为普通 Web UI 日志流。 |
| `show-logs-frontend` | 查看前端日志 | `./planet.sh log -f` | 更适合 CLI/运维工具,不建议作为普通 Web UI 日志流。 |
## 默认不暴露到 UI 的能力
除非有明确产品需求并经过额外安全评审,否则以下脚本能力不应直接暴露到 Web UI
- `./planet.sh restart`
- `./planet.sh start`
- `./planet.sh stop`
- `./planet.sh createuser`
- 任何未来的原始 shell 透传能力
原因:
- 全量重启可能打断当前控制会话;
- stop/start 影响面更大;
- 用户创建不是服务控制操作;
- 原始 shell 透传会引入不必要的权限风险。
## 第一阶段推荐 UI 契约
### 前端 action payload
```json
{
"action": "restart-backend"
}
```
### 后端命令解析
```text
restart-backend -> ["./planet.sh", "restart", "-b"]
restart-database -> ["./planet.sh", "restart", "-d"]
restart-system -> ["./planet.sh", "restart"]
restart-frontend -> ["./planet.sh", "restart", "-f"]
health-check -> ["./planet.sh", "health"]
```
## API 草案
### 主接口
- `POST /api/v1/system/restart-tasks`
用途:
- 创建受控重启任务;
- 将白名单 action 解析成固定 `planet.sh` 命令;
- 把执行交给外部 runner 或 detached subprocess。
### 请求体
```json
{
"action": "restart-backend"
}
```
未来可选形态:
```json
{
"action": "restart-backend-port",
"port": 8000
}
```
### 响应
```json
{
"task_id": "restart_20260331_153000_ab12cd",
"action": "restart-backend",
"status": "queued",
"stage": "accepted",
"message": "Restart task accepted"
}
```
### 任务查询接口
- `GET /api/v1/system/restart-tasks/{task_id}`
响应结构:
```json
{
"task_id": "restart_20260331_153000_ab12cd",
"action": "restart-backend",
"status": "queued",
"stage": "accepted",
"message": "Waiting for execution",
"requested_by": {
"id": 1,
"username": "admin"
},
"created_at": "2026-03-31T15:30:00+08:00",
"updated_at": "2026-03-31T15:30:02+08:00"
}
```
### 可选日志接口
- `GET /api/v1/system/restart-tasks/{task_id}/logs`
建议响应:
```json
{
"task_id": "restart_20260331_153000_ab12cd",
"lines": [
"accepted restart-backend request",
"spawning restart command",
"waiting for backend shutdown",
"waiting for backend health recovery"
]
}
```
日志接口在第一阶段不是必需项。首版可以只依赖任务状态加 `/health` 轮询。
## 任务状态模型
### Status
- `queued`
- `running`
- `succeeded`
- `failed`
- `timeout`
### Stage
- `accepted`
- `spawning`
- `stopping`
- `starting`
- `waiting_for_health`
- `healthy`
- `failed`
### 含义
- `status` 是高层终态/非终态状态。
- `stage` 是面向运维人员和 UI 的执行阶段。
- `message` 是 modal 或全屏遮罩中展示的短文本。
## 权限模型
- `restart-backend` 应要求 `super_admin`
- 权限检查应沿用 [users.py](/home/ray/dev/linkong/planet/backend/app/api/v1/users.py) 中已有的角色模式。
- 前端可以对非 `super_admin` 隐藏控件,但后端必须继续强制鉴权。
## 存储模型
推荐第一阶段实现:
- 将重启任务状态存入 Redis
- 任务生命周期保持较短;
- 最近日志用有界列表保存。
建议 key
- `system:restart_task:{task_id}`
- `system:restart_task:{task_id}:logs`
建议字段:
- `task_id`
- `action`
- `status`
- `stage`
- `message`
- `requested_by_id`
- `requested_by_username`
- `created_at`
- `updated_at`
## 执行模型
处理请求的 API 进程不应依赖自身持续存活来流式输出完整重启日志。
推荐执行流程:
1. 校验调用方和 action
2. 在 Redis 中创建任务状态
3. 将 action 解析为固定 `planet.sh` argv
4. 启动 detached executor
5. 返回 `task_id`
6. executor 在重启过程中更新任务状态
7. 前端轮询健康状态和/或任务状态,直到服务恢复
推荐命令解析示例:
```text
restart-backend -> ["./planet.sh", "restart", "-b"]
restart-frontend -> ["./planet.sh", "restart", "-f"]
restart-backend-port -> ["./planet.sh", "restart", "-b", "<port>"]
health-check -> ["./planet.sh", "health"]
```
## 前端轮询流程
推荐第一阶段 UX
1. 用户点击 `重启后端`
2. 确认 modal 说明服务会短暂不可用
3. 前端调用 `POST /api/v1/system/restart-tasks`
4. UI 进入阻塞式重启状态
5. 前端每 `1-2s` 轮询 `/health`
6. 临时请求失败视为预期现象
7. 连续 `2-3` 次健康检查成功后,前端刷新页面
可选增强轮询:
1. 后端仍可达时轮询任务状态接口
2. 断连开始后切换为 `/health` 恢复轮询
3. 健康恢复后刷新页面
## 前端状态机
- `idle`
- `confirming`
- `submitting`
- `waiting_for_shutdown`
- `waiting_for_recovery`
- `recovered`
- `failed`
- `timeout`
建议 UI 文案:
- `已发送重启指令`
- `正在停止后端服务`
- `正在等待服务恢复`
- `服务已恢复,正在刷新页面`
- `恢复超时,请手动检查服务状态`
## 当前 Dashboard 实现
Dashboard 当前已实现:
- `restart-backend`
- `restart-frontend`
- `restart-ai-provider`
- `restart-database`
- `restart-system`
- `super_admin` 权限门禁
- 任务创建接口
- Redis 任务状态
- 前端确认 modal
- 后端 `/health` 轮询
- 前端入口轮询
- 恢复后自动刷新页面
暂不实现:
- 原始 shell 命令透传
- 任意服务控制
- 完整终端 stdout 流式输出
- 多 action 并发重启队列
## 实现清单
### 后端
1.`backend/app/api/v1/` 下新增专用系统控制 API 模块
2. 增加基于白名单的 `planet.sh` action 解析器
3. 将重启任务状态存入 Redis
4. 增加 detached restart-runner 脚本执行
5. 暴露:
- `POST /api/v1/system/restart-tasks`
- `GET /api/v1/system/restart-tasks/{task_id}`
- 可选任务日志接口
6. 对所有 restart-task 接口强制 `super_admin` 权限
### 前端
1. 在 dashboard 为 `super_admin` 增加 `重启服务` 控件
2. 发送前展示确认 modal
3. 提交后将 modal 切换为阻塞式重启状态
4. 后端重启使用 `/health` 轮询确认恢复
5. 前端重启和完全重启使用前端入口探测确认恢复
6. 连续健康检查成功后自动刷新页面
7. 展示简短阶段日志,而不是原始终端流
### 运维说明
1. 优先使用局部重启,只有确实需要时才执行完全重启
2. 前端重启会打断当前页面入口,必须进入恢复等待状态
3. 命令执行必须始终从仓库根目录发起
4. API 边界只能传递固定 action 名称
## 校验要求
- 拒绝任何不在白名单中的 action。
- 如果增加带端口 action端口必须校验为 `1..65535` 的整数。
- 从仓库根目录解析命令,确保 `planet.sh` 的工作目录稳定。
- detached runner 使用 `zsh -ic` 执行白名单命令,确保 `~/.zshrc` 中的本地环境变量进入重启流程。
- 记录请求 action、操作者身份、执行开始时间和结果。