mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 15:46:38 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d63dd4cc3 | |||
| 4cfa6adee7 |
@@ -0,0 +1,178 @@
|
||||
# Proxy Protocol 传输分析报告
|
||||
|
||||
**日期**: 2026-05-07
|
||||
**测试环境**: 20.118.172.127 (Server 1) ↔ 108.181.90.137 (Server 2)
|
||||
|
||||
---
|
||||
|
||||
## 1. 代码流程分析
|
||||
|
||||
### 完整数据链路
|
||||
|
||||
```
|
||||
前端 (proxyProtocol: 0|1|2)
|
||||
→ 后端 handler mutations.go:1936
|
||||
→ 数据库存储 forward.proxy_protocol (model.go:50)
|
||||
→ 控制面 buildForwardServiceConfigs (control_plane.go:1791-1796)
|
||||
→ handler metadata: {"proxyProtocol": 2}
|
||||
→ Agent metadata 解析 (metadata.go:42)
|
||||
→ handler.go:256 WrapClientConn()
|
||||
→ conn.go:14 HeaderProxyFromAddrs(byte(ppv), src, dst)
|
||||
→ conn.go:15 header.WriteTo(c)
|
||||
→ 目标服务器收到 PROXY protocol header
|
||||
```
|
||||
|
||||
### 关键代码
|
||||
|
||||
**写入 PROXY header** (`go-gost/x/internal/net/proxyproto/conn.go`):
|
||||
```go
|
||||
func WrapClientConn(ppv int, src, dst net.Addr, c net.Conn) net.Conn {
|
||||
if ppv <= 0 {
|
||||
return c
|
||||
}
|
||||
header := proxyproto.HeaderProxyFromrs(byte(ppv), src, dst)
|
||||
header.WriteTo(c)
|
||||
return c
|
||||
}
|
||||
```
|
||||
|
||||
**Handler 调用** (`go-gost/x/handler/forward/local/handler.go:256`):
|
||||
```go
|
||||
cc = proxyproto.WrapClientConn(h.md.proxyProtocol, conn.RemoteAddr(), conn.LocalAddr(), cc)
|
||||
```
|
||||
|
||||
- `src` = `conn.RemoteAddr()` → 客户端真实 IP ✅
|
||||
- `dst` = `conn.LocalAddr()` → agent 监听地址 ✅
|
||||
- `ppv` = 1 或 2 → 版本号正确 ✅
|
||||
|
||||
---
|
||||
|
||||
## 2. 实际传输测试结果
|
||||
|
||||
### 测试方法
|
||||
|
||||
1. 在 Server 2 启动 Python TCP 监听器,解析 PROXY protocol header
|
||||
2. 在 Server 1 用当前代码编译 gost,配置 `proxyProtocol: 2` 转发到 Server 2
|
||||
3. 通过 `nc` 发送测试数据,验证 Server 2 是否收到正确的 PROXY header
|
||||
|
||||
### 测试结果
|
||||
|
||||
| 版本 | 状态 | 接收到的 Header |
|
||||
|------|------|----------------|
|
||||
| **PPv2** | ✅ 成功 | `PP2 family=1 alen=12 SRC=127.0.0.1:45410 DST=127.0.0.1:20001` |
|
||||
| **PPv1** | ✅ 成功 | `PROXY TCP4 127.0.0.1 127.0.0.1 43816 20001` |
|
||||
|
||||
### 测试详情
|
||||
|
||||
**PPv2 原始数据**:
|
||||
```
|
||||
Got 28 bytes
|
||||
PP2 family=1 alen=12
|
||||
SRC=127.0.0.1:45410 DST=127.0.0.1:20001
|
||||
```
|
||||
|
||||
**PPv1 原始数据** (hex):
|
||||
```
|
||||
50524f58592054435034203132372e302e302e31203132372e302e302e312034333831362032303030310d0a
|
||||
```
|
||||
解码: `PROXY TCP4 127.0.0.1 127.0.0.1 43816 20001`
|
||||
|
||||
---
|
||||
|
||||
## 3. 单元测试结果
|
||||
|
||||
```
|
||||
go-gost/x/handler/forward/local/ → TestLocalForwardHandlerSendsProxyProtocolToTarget ✅
|
||||
go-backend/internal/http/handler/ → TestBuildForwardServiceConfigsSendsProxyProtocolToForwardHandler ✅
|
||||
go-backend/internal/store/repo/ → TestGetForwardRecordIncludesProxyProtocol ✅
|
||||
```
|
||||
|
||||
全部通过 (3/3)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 发现的问题
|
||||
|
||||
### 问题 1: `WriteTo` 错误未检查
|
||||
|
||||
**位置**: `go-gost/x/internal/net/proxyproto/conn.go:15`
|
||||
|
||||
```go
|
||||
header.WriteTo(c) // 返回 (int64, error) 被忽略
|
||||
```
|
||||
|
||||
**影响**: 如果写入失败(连接已断开、网络错误等),后续数据传输会在没有 PROXY header 的情况下继续,目标服务器可能解析出错。
|
||||
|
||||
**建议**:
|
||||
```go
|
||||
if _, err := header.WriteTo(c); err != nil {
|
||||
return c // 或包装一个带错误的 conn
|
||||
}
|
||||
```
|
||||
|
||||
**严重程度**: 低(实际场景中,写入失败后 `Transport` 也会很快失败)
|
||||
|
||||
---
|
||||
|
||||
### 问题 2: 部署版本过旧
|
||||
|
||||
**服务器状态**:
|
||||
|
||||
| 服务器 | 组件 | 版本 | 状态 |
|
||||
|--------|------|------|------|
|
||||
| 20.118.172.127 | flux_agent | UPX 压缩,无法读取版本 | ✅ 运行中 |
|
||||
| 20.118.172.127 | paneld | `/app/paneld` | ✅ 运行中 |
|
||||
| 20.118.172.127 | /usr/local/bin/gost | v3.0.0 (go1.23.4) | 旧版,不支持 handler metadata 中的 proxyProtocol |
|
||||
| 108.181.90.137 | flux_agent | 8.8MB | ✅ 运行中 |
|
||||
|
||||
**影响**: 旧版 gost 二进制不识别 handler metadata 中的 `proxyProtocol` 字段,PROXY protocol 功能在生产环境不可用。
|
||||
|
||||
**验证**: 用旧版 gost 测试时,目标服务器收到的原始数据为空,无 PROXY header。
|
||||
|
||||
---
|
||||
|
||||
### 问题 3: 数据库 Schema 缺失
|
||||
|
||||
**位置**: 20.118.172.127 的 `/app/data/gost.db`
|
||||
|
||||
**当前 forward 表 schema**:
|
||||
```sql
|
||||
CREATE TABLE `forward` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` integer NOT NULL,
|
||||
`user_name` varchar(100) NOT NULL,
|
||||
`name` varchar(100) NOT NULL,
|
||||
`tunnel_id` integer NOT NULL,
|
||||
`remote_addr` text NOT NULL,
|
||||
`strategy` varchar(100) NOT NULL DEFAULT "fifo",
|
||||
`in_flow` integer NOT NULL DEFAULT 0,
|
||||
`out_flow` integer NOT NULL DEFAULT 0,
|
||||
`created_time` integer NOT NULL,
|
||||
`updated_time` integer NOT NULL,
|
||||
`status` integer NOT NULL,
|
||||
`inx` integer NOT NULL DEFAULT 0,
|
||||
`speed_id` integer
|
||||
);
|
||||
```
|
||||
|
||||
**缺失字段**:
|
||||
- `proxy_protocol` — PROXY protocol 版本
|
||||
- `max_conn` — 最大连接数
|
||||
- `ip_max_conn` — 每 IP 最大连接数
|
||||
- `ip_speed_id` — 每 IP 限速 ID
|
||||
|
||||
**影响**: 后端无法存储和读取 proxy_protocol 配置,前端设置不会生效。
|
||||
|
||||
---
|
||||
|
||||
## 5. 结论
|
||||
|
||||
| 维度 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| **代码实现** | ✅ 正确 | 完整的写入链路,版本/地址正确 |
|
||||
| **单元测试** | ✅ 通过 | 3/3 测试覆盖 handler、repo、控制面 |
|
||||
| **实际传输 (新编译版)** | ✅ 成功 | PPv1 和 PPv2 均正确传输 |
|
||||
| **实际传输 (部署版)** | ❌ 不工作 | 旧版不支持 handler metadata 中的 proxyProtocol |
|
||||
| **数据库 Schema** | ❌ 缺字段 | 需要迁移添加 proxy_protocol 等列 |
|
||||
|
||||
**总结**: 代码实现正确,PROXY protocol 传输逻辑无误。但生产服务器运行的是旧版本,需要升级 backend 和 agent 才能启用此功能。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,295 @@
|
||||
# 面板本体一键升级设计
|
||||
|
||||
**日期**: 2026-05-04
|
||||
**状态**: 待审核
|
||||
**作者**: AI Assistant
|
||||
|
||||
## 概述
|
||||
|
||||
在 FLVX 管理面板中增加“面板升级”能力,使管理员可以在网页上检查 GitHub Release 并触发面板本体升级。目标是升级整套面板,而不是只升级转发节点或只替换后端二进制。
|
||||
|
||||
本设计采用 Docker Compose 整套升级方案:后端容器通过受限的 Docker socket 能力更新宿主机部署目录中的 `docker-compose.yml` 和 `.env`,然后启动独立的升级 helper 容器,由 helper 拉取新版 backend/frontend 镜像并重新启动 `backend` 与 `frontend` 服务。
|
||||
|
||||
## sub2api 参考结论
|
||||
|
||||
sub2api 的一键升级不是在宿主机执行 `docker compose pull/up`。它的运行形态是单体 Go 服务:前端构建产物 embed 到后端二进制,容器内只运行 `/app/sub2api`。升级接口下载 GitHub Release 中匹配当前系统和架构的 `sub2api_<version>_<os>_<arch>.tar.gz` 以及 `checksums.txt`,校验后把当前 `os.Executable()` 指向的 `/app/sub2api` 改名为 `/app/sub2api.backup`,再把新二进制原子替换到原路径。重启接口延迟调用 `os.Exit(0)`,依赖 Docker Compose 的 `restart: unless-stopped` 拉起同一个容器。
|
||||
|
||||
这种方式在 sub2api 的 Docker 部署中可行,是因为它的前后端在同一个二进制里。FLVX 当前是 `flux-panel-backend` 与 `vite-frontend` 两个容器,替换 `/app/paneld` 只能升级后端,不能升级前端页面。因此 FLVX 的“面板本体升级”需要更新 Compose 版本和两个镜像,而不是照搬二进制替换。
|
||||
|
||||
## 目标
|
||||
|
||||
1. 管理员可在面板上查看当前版本、最新版本、升级通道和升级能力状态。
|
||||
2. 管理员可一键升级整套面板 backend/frontend。
|
||||
3. 升级复用现有 GitHub Release 和 `FLUX_VERSION` 版本机制。
|
||||
4. 升级复用现有 GitHub 加速配置 `github_proxy_enabled` / `github_proxy_url`。
|
||||
5. 升级过程不接受任意命令、任意 URL 或任意 compose 路径。
|
||||
6. 环境不满足时清晰提示不可用原因,不静默失败。
|
||||
|
||||
## 非目标
|
||||
|
||||
1. 不实现 sub2api 式后端二进制替换作为本次主路径。
|
||||
2. 不支持从非本仓库 Release 下载升级资产。
|
||||
3. 不支持普通用户触发升级。
|
||||
4. 不支持在前端执行 shell 命令。
|
||||
5. 不修改 `install.sh` 或 `panel_install.sh` 的本地安装菜单逻辑;发布流程仍可能覆盖这些脚本。
|
||||
6. 不引入前端测试框架。
|
||||
|
||||
## 影响范围
|
||||
|
||||
### 后端
|
||||
|
||||
- `go-backend/internal/http/handler/handler.go`
|
||||
- `go-backend/internal/http/handler/upgrade.go`
|
||||
- 新增 `go-backend/internal/http/handler/system_upgrade.go`
|
||||
- 新增 `go-backend/internal/http/handler/system_upgrade_test.go`
|
||||
- `go-backend/Dockerfile`
|
||||
|
||||
### 部署模板
|
||||
|
||||
- `docker-compose-v4.yml`
|
||||
- `docker-compose-v6.yml`
|
||||
|
||||
### 前端
|
||||
|
||||
- `vite-frontend/src/api/index.ts`
|
||||
- `vite-frontend/src/api/types.ts`
|
||||
- `vite-frontend/src/pages/config.tsx`
|
||||
|
||||
## 运行前提
|
||||
|
||||
升级能力仅在 Docker Compose 部署中可用,并要求后端容器具备以下条件:
|
||||
|
||||
1. 容器内存在 Docker CLI,且支持 `docker compose version`。
|
||||
2. `/var/run/docker.sock` 挂载到后端容器。
|
||||
3. 宿主部署目录挂载到容器内固定路径,例如 `/opt/flvx-panel`。
|
||||
4. 环境变量 `PANEL_DEPLOY_DIR=/opt/flvx-panel`。
|
||||
5. 环境变量 `PANEL_BACKEND_CONTAINER=flux-panel-backend`,为空时默认使用 `flux-panel-backend`;值必须匹配容器名安全字符集 `[A-Za-z0-9_.-]+`。
|
||||
6. 部署目录内存在 `.env` 和 `docker-compose.yml`。
|
||||
|
||||
如果任一条件不满足,检查接口返回 `capable=false` 和明确的 `reason`,升级按钮禁用。
|
||||
|
||||
## 后端设计
|
||||
|
||||
### API
|
||||
|
||||
新增系统升级接口,路径使用 `/api/v1/system/*`,继续受现有 middleware 管控,仅管理员可访问。
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| `POST` | `/api/v1/system/version` | 返回当前版本、升级通道、能力状态和可选最新版本 |
|
||||
| `POST` | `/api/v1/system/check-updates` | 强制查询 GitHub Release,返回最新版本和候选列表 |
|
||||
| `POST` | `/api/v1/system/upgrade` | 执行升级 |
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel": "stable",
|
||||
"version": ""
|
||||
}
|
||||
```
|
||||
|
||||
`channel` 使用现有节点升级的通道语义:`stable` 匹配纯数字版本,`dev` 匹配 `alpha` / `beta` / `rc`。`version` 为空时自动选择该通道最新 Release。
|
||||
|
||||
`/api/v1/system/version` 返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"currentVersion": "2.1.9-beta14",
|
||||
"channel": "stable",
|
||||
"latestVersion": "2.1.9",
|
||||
"hasUpdate": true,
|
||||
"capable": true,
|
||||
"reason": "",
|
||||
"deployDir": "/opt/flvx-panel",
|
||||
"composeFile": "/opt/flvx-panel/docker-compose.yml",
|
||||
"backendContainer": "flux-panel-backend"
|
||||
}
|
||||
```
|
||||
|
||||
`/api/v1/system/upgrade` 成功返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.1.9",
|
||||
"message": "升级 helper 已启动,面板服务将短暂重启",
|
||||
"commands": [
|
||||
"docker run -d --rm --volumes-from flux-panel-backend ...",
|
||||
"docker compose pull backend frontend",
|
||||
"docker compose up -d backend frontend"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
返回的 `commands` 只用于 UI 展示固定步骤,不包含用户输入或 shell 拼接结果。
|
||||
|
||||
### 版本来源
|
||||
|
||||
当前版本优先从容器环境变量读取:
|
||||
|
||||
1. `FLUX_VERSION`
|
||||
2. `VITE_APP_VERSION` 不在后端容器中可靠存在,不作为后端版本来源。
|
||||
3. 为空时返回 `dev`。
|
||||
|
||||
发布流程已经在 `panel_install.sh` 写入 `.env` 的 `FLUX_VERSION`,Compose 模板需要把该变量传给 backend 容器,保证后端可感知当前版本。
|
||||
|
||||
### Release 查询
|
||||
|
||||
复用现有 `fetchGitHubReleases`、`resolveLatestReleaseByChannel`、`normalizeReleaseChannel`、`releaseChannelFromTag`、`releaseChannelLabel` 和 GitHub 加速配置能力。新增函数只负责筛选系统升级所需资产:
|
||||
|
||||
- `docker-compose-v4.yml`
|
||||
- `docker-compose-v6.yml`
|
||||
|
||||
是否下载 v4/v6 compose 文件通过当前部署目录中的 `docker-compose.yml` 判断:如果网络定义包含 `enable_ipv6: true`,选择 `docker-compose-v6.yml`;否则选择 `docker-compose-v4.yml`。
|
||||
|
||||
### 升级执行器
|
||||
|
||||
新增 `systemUpgradeExecutor`,职责明确分为可测试的小函数:
|
||||
|
||||
1. `checkSystemUpgradeCapability()` 检查 Docker CLI、Docker socket、部署目录、`.env`、`docker-compose.yml`。
|
||||
2. `selectComposeAsset(currentCompose []byte) string` 选择 v4/v6 compose 资产。
|
||||
3. `updateEnvVersion(path, version string) error` 原子更新 `.env` 中的 `FLUX_VERSION`。
|
||||
4. `downloadCompose(version, assetName, dest string) error` 下载新版 compose 模板到临时文件。
|
||||
5. `currentBackendImage(containerName string) (string, error)` 获取当前 backend 容器镜像 ID。
|
||||
6. `startSystemUpgradeHelper(version string) error` 启动独立 helper 容器执行固定升级流程。
|
||||
|
||||
升级流程:
|
||||
|
||||
1. 获取全局升级锁,拒绝并发升级。
|
||||
2. 校验目标版本存在且不是 draft。
|
||||
3. 检查升级能力。
|
||||
4. 备份 `.env` 为 `.env.upgrade.bak`,备份 `docker-compose.yml` 为 `docker-compose.yml.upgrade.bak`。
|
||||
5. 下载目标版本的 compose 文件到部署目录临时文件。
|
||||
6. 原子替换 `docker-compose.yml`。
|
||||
7. 原子更新 `.env` 的 `FLUX_VERSION`。
|
||||
8. 通过 Docker socket 查询当前 backend 容器的镜像 ID。
|
||||
9. 使用当前 backend 镜像启动一个不属于 Compose 项目的临时 helper 容器。
|
||||
10. helper 通过 `--volumes-from flux-panel-backend` 继承部署目录挂载,并显式挂载 `/var/run/docker.sock`。
|
||||
11. helper 在 `PANEL_DEPLOY_DIR` 下执行 `docker compose pull backend frontend`。
|
||||
12. helper 等待 5 秒,让 SQLite WAL 等文件刷盘。
|
||||
13. helper 执行 `docker compose up -d backend frontend`,由 Compose 重建前端和后端。
|
||||
14. 后端接口在 helper 成功启动后立即返回;浏览器随后会经历短暂断线。
|
||||
|
||||
PostgreSQL 模式不主动 pull 或重建 `postgres` 服务,避免无关数据库变动。新版 compose 文件仍保留 postgres 配置供后续手动迁移或重建使用。
|
||||
|
||||
### 命令安全
|
||||
|
||||
后端不暴露通用命令执行能力。后端只直接执行 Docker CLI 的固定参数,用于获取当前镜像和启动 helper:
|
||||
|
||||
```go
|
||||
exec.CommandContext(ctx, "docker", "inspect", "-f", "{{.Image}}", backendContainer)
|
||||
exec.CommandContext(ctx, "docker", "run", "-d", "--rm", "--name", helperName,
|
||||
"--volumes-from", backendContainer,
|
||||
"-v", "/var/run/docker.sock:/var/run/docker.sock",
|
||||
"-e", "PANEL_DEPLOY_DIR=/opt/flvx-panel",
|
||||
"--entrypoint", "/bin/sh", imageID,
|
||||
"-c", helperScript)
|
||||
```
|
||||
|
||||
`helperScript` 由后端固定生成,不拼接用户输入:
|
||||
|
||||
```sh
|
||||
cd "$PANEL_DEPLOY_DIR" && docker compose pull backend frontend && sleep 5 && docker compose up -d backend frontend
|
||||
```
|
||||
|
||||
工作目录固定为 `PANEL_DEPLOY_DIR`。`PANEL_DEPLOY_DIR` 必须是绝对路径,且必须包含 `.env` 和 `docker-compose.yml`。接口输入只允许影响 `channel` 和已验证的 Release `version`。
|
||||
|
||||
### 超时和错误处理
|
||||
|
||||
1. Release 查询超时沿用现有 GitHub API 客户端超时。
|
||||
2. 下载 compose 文件使用 60 秒超时。
|
||||
3. 启动 helper 使用 30 秒超时,helper 内部命令不受原 HTTP 请求生命周期影响。
|
||||
4. 任一步失败时返回错误信息,并尽量保留 `.upgrade.bak` 供人工恢复。
|
||||
5. 如果 `.env` 更新后后续步骤失败,不自动回滚镜像或容器,避免误判导致更大破坏;错误信息提示备份文件位置。
|
||||
|
||||
## 部署模板设计
|
||||
|
||||
`docker-compose-v4.yml` 和 `docker-compose-v6.yml` 的 backend 服务增加:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
FLUX_VERSION: ${FLUX_VERSION:-dev}
|
||||
PANEL_DEPLOY_DIR: /opt/flvx-panel
|
||||
PANEL_BACKEND_CONTAINER: flux-panel-backend
|
||||
volumes:
|
||||
- sqlite_data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./:/opt/flvx-panel
|
||||
```
|
||||
|
||||
`go-backend/Dockerfile` 的 runtime 镜像通过多阶段构建从官方 `docker:27-cli` 镜像复制 Docker CLI 和 compose 插件到 Debian runtime 镜像,避免依赖 Debian apt 源中的 Docker 包可用性:
|
||||
|
||||
```dockerfile
|
||||
FROM docker:27-cli AS dockercli
|
||||
FROM debian:bookworm-slim
|
||||
COPY --from=dockercli /usr/local/bin/docker /usr/local/bin/docker
|
||||
COPY --from=dockercli /usr/local/libexec/docker/cli-plugins/docker-compose /usr/local/libexec/docker/cli-plugins/docker-compose
|
||||
```
|
||||
|
||||
实现时保留现有 Go builder 和 `/app/paneld` 入口,仅增加 Docker CLI stage 和复制步骤。
|
||||
|
||||
helper 容器使用当前 backend 容器的镜像 ID 启动,而不是额外依赖 `docker:cli` 镜像。这样不引入新的镜像仓库依赖,并保证 helper 内可用的 Docker CLI 与当前后端一致。
|
||||
|
||||
## 前端设计
|
||||
|
||||
在 `vite-frontend/src/pages/config.tsx` 的基本设置或数据库占用附近增加“面板升级”卡片,避免隐藏在节点页导致误解为“节点升级”。
|
||||
|
||||
展示内容:
|
||||
|
||||
1. 当前版本。
|
||||
2. 最新版本。
|
||||
3. 更新通道选择,复用现有 `stable` / `dev` 语义和 `UpdateReleaseChannel` 本地存储。
|
||||
4. 升级能力状态:可用、不可用原因、Docker socket 高权限提示。
|
||||
5. 操作按钮:检查更新、立即升级。
|
||||
|
||||
交互:
|
||||
|
||||
1. 页面加载时调用 `/system/version`。
|
||||
2. 点击“检查更新”调用 `/system/check-updates`。
|
||||
3. 点击“立即升级”前弹出确认框,明确提示服务会短暂中断,并提示 Docker socket 具备宿主高权限。
|
||||
4. 升级请求只等待 helper 启动,超时设置为 60 秒。
|
||||
5. 成功后 toast 显示“升级已触发,面板将在数十秒内重启”,并可提示用户稍后刷新。
|
||||
|
||||
## 安全边界
|
||||
|
||||
Docker socket 挂载等同于给后端容器宿主机级别控制能力。这是本设计的主要风险。缓解措施:
|
||||
|
||||
1. 仅 `/api/v1/system/*` 管理员接口可触发。
|
||||
2. 不提供任意命令执行接口。
|
||||
3. 不允许用户传入下载 URL。
|
||||
4. 不允许用户传入 compose 路径。
|
||||
5. 只升级本仓库 GitHub Release,且跳过 draft。
|
||||
6. 前端明确展示 Docker socket 权限提示。
|
||||
|
||||
## 测试策略
|
||||
|
||||
### Go 单测
|
||||
|
||||
新增 `system_upgrade_test.go` 覆盖:
|
||||
|
||||
1. `selectComposeAsset` 对 v4/v6 compose 内容的判断。
|
||||
2. `.env` 中已有 `FLUX_VERSION` 时更新值。
|
||||
3. `.env` 中缺少 `FLUX_VERSION` 时追加值。
|
||||
4. 缺少部署目录、`.env`、`docker-compose.yml`、Docker socket 时返回不可用原因。
|
||||
5. helper 命令构造固定命令序列,不拼接用户输入。
|
||||
6. 并发升级锁会拒绝第二个升级请求。
|
||||
|
||||
### 手动/集成验证
|
||||
|
||||
1. `go-backend`: `go test ./...`
|
||||
2. `vite-frontend`: `pnpm run build`
|
||||
3. 本地容器验证:启动 Compose 后检查设置页升级卡片可显示能力状态。
|
||||
4. 在无 Docker socket 的开发环境验证按钮禁用并显示原因。
|
||||
|
||||
## 回滚与恢复
|
||||
|
||||
自动升级失败时不做自动容器回滚。后端会保留:
|
||||
|
||||
1. `.env.upgrade.bak`
|
||||
2. `docker-compose.yml.upgrade.bak`
|
||||
|
||||
人工恢复步骤由错误信息提示:进入部署目录,按需恢复备份文件,再执行 `docker compose up -d backend frontend`。
|
||||
|
||||
## 决策记录
|
||||
|
||||
本设计已确定采用 Docker socket 整套升级方案,不再保留二进制替换作为本次实现路径。Docker socket 的权限风险通过管理员限制、命令白名单和前端提示控制。
|
||||
Reference in New Issue
Block a user