13 KiB
面板本体一键升级设计
日期: 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 版本和两个镜像,而不是照搬二进制替换。
目标
- 管理员可在面板上查看当前版本、最新版本、升级通道和升级能力状态。
- 管理员可一键升级整套面板 backend/frontend。
- 升级复用现有 GitHub Release 和
FLUX_VERSION版本机制。 - 升级复用现有 GitHub 加速配置
github_proxy_enabled/github_proxy_url。 - 升级过程不接受任意命令、任意 URL 或任意 compose 路径。
- 环境不满足时清晰提示不可用原因,不静默失败。
非目标
- 不实现 sub2api 式后端二进制替换作为本次主路径。
- 不支持从非本仓库 Release 下载升级资产。
- 不支持普通用户触发升级。
- 不支持在前端执行 shell 命令。
- 不修改
install.sh或panel_install.sh的本地安装菜单逻辑;发布流程仍可能覆盖这些脚本。 - 不引入前端测试框架。
影响范围
后端
go-backend/internal/http/handler/handler.gogo-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.ymldocker-compose-v6.yml
前端
vite-frontend/src/api/index.tsvite-frontend/src/api/types.tsvite-frontend/src/pages/config.tsx
运行前提
升级能力仅在 Docker Compose 部署中可用,并要求后端容器具备以下条件:
- 容器内存在 Docker CLI,且支持
docker compose version。 /var/run/docker.sock挂载到后端容器。- 宿主部署目录挂载到容器内固定路径,例如
/opt/flvx-panel。 - 环境变量
PANEL_DEPLOY_DIR=/opt/flvx-panel。 - 环境变量
PANEL_BACKEND_CONTAINER=flux-panel-backend,为空时默认使用flux-panel-backend;值必须匹配容器名安全字符集[A-Za-z0-9_.-]+。 - 部署目录内存在
.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 |
执行升级 |
请求体:
{
"channel": "stable",
"version": ""
}
channel 使用现有节点升级的通道语义:stable 匹配纯数字版本,dev 匹配 alpha / beta / rc。version 为空时自动选择该通道最新 Release。
/api/v1/system/version 返回:
{
"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 成功返回:
{
"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 拼接结果。
版本来源
当前版本优先从容器环境变量读取:
FLUX_VERSIONVITE_APP_VERSION不在后端容器中可靠存在,不作为后端版本来源。- 为空时返回
dev。
发布流程已经在 panel_install.sh 写入 .env 的 FLUX_VERSION,Compose 模板需要把该变量传给 backend 容器,保证后端可感知当前版本。
Release 查询
复用现有 fetchGitHubReleases、resolveLatestReleaseByChannel、normalizeReleaseChannel、releaseChannelFromTag、releaseChannelLabel 和 GitHub 加速配置能力。新增函数只负责筛选系统升级所需资产:
docker-compose-v4.ymldocker-compose-v6.yml
是否下载 v4/v6 compose 文件通过当前部署目录中的 docker-compose.yml 判断:如果网络定义包含 enable_ipv6: true,选择 docker-compose-v6.yml;否则选择 docker-compose-v4.yml。
升级执行器
新增 systemUpgradeExecutor,职责明确分为可测试的小函数:
checkSystemUpgradeCapability()检查 Docker CLI、Docker socket、部署目录、.env、docker-compose.yml。selectComposeAsset(currentCompose []byte) string选择 v4/v6 compose 资产。updateEnvVersion(path, version string) error原子更新.env中的FLUX_VERSION。downloadCompose(version, assetName, dest string) error下载新版 compose 模板到临时文件。currentBackendImage(containerName string) (string, error)获取当前 backend 容器镜像 ID。startSystemUpgradeHelper(version string) error启动独立 helper 容器执行固定升级流程。
升级流程:
- 获取全局升级锁,拒绝并发升级。
- 校验目标版本存在且不是 draft。
- 检查升级能力。
- 备份
.env为.env.upgrade.bak,备份docker-compose.yml为docker-compose.yml.upgrade.bak。 - 下载目标版本的 compose 文件到部署目录临时文件。
- 原子替换
docker-compose.yml。 - 原子更新
.env的FLUX_VERSION。 - 通过 Docker socket 查询当前 backend 容器的镜像 ID。
- 使用当前 backend 镜像启动一个不属于 Compose 项目的临时 helper 容器。
- helper 通过
--volumes-from flux-panel-backend继承部署目录挂载,并显式挂载/var/run/docker.sock。 - helper 在
PANEL_DEPLOY_DIR下执行docker compose pull backend frontend。 - helper 等待 5 秒,让 SQLite WAL 等文件刷盘。
- helper 执行
docker compose up -d backend frontend,由 Compose 重建前端和后端。 - 后端接口在 helper 成功启动后立即返回;浏览器随后会经历短暂断线。
PostgreSQL 模式不主动 pull 或重建 postgres 服务,避免无关数据库变动。新版 compose 文件仍保留 postgres 配置供后续手动迁移或重建使用。
命令安全
后端不暴露通用命令执行能力。后端只直接执行 Docker CLI 的固定参数,用于获取当前镜像和启动 helper:
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 由后端固定生成,不拼接用户输入:
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。
超时和错误处理
- Release 查询超时沿用现有 GitHub API 客户端超时。
- 下载 compose 文件使用 60 秒超时。
- 启动 helper 使用 30 秒超时,helper 内部命令不受原 HTTP 请求生命周期影响。
- 任一步失败时返回错误信息,并尽量保留
.upgrade.bak供人工恢复。 - 如果
.env更新后后续步骤失败,不自动回滚镜像或容器,避免误判导致更大破坏;错误信息提示备份文件位置。
部署模板设计
docker-compose-v4.yml 和 docker-compose-v6.yml 的 backend 服务增加:
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 包可用性:
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 的基本设置或数据库占用附近增加“面板升级”卡片,避免隐藏在节点页导致误解为“节点升级”。
展示内容:
- 当前版本。
- 最新版本。
- 更新通道选择,复用现有
stable/dev语义和UpdateReleaseChannel本地存储。 - 升级能力状态:可用、不可用原因、Docker socket 高权限提示。
- 操作按钮:检查更新、立即升级。
交互:
- 页面加载时调用
/system/version。 - 点击“检查更新”调用
/system/check-updates。 - 点击“立即升级”前弹出确认框,明确提示服务会短暂中断,并提示 Docker socket 具备宿主高权限。
- 升级请求只等待 helper 启动,超时设置为 60 秒。
- 成功后 toast 显示“升级已触发,面板将在数十秒内重启”,并可提示用户稍后刷新。
安全边界
Docker socket 挂载等同于给后端容器宿主机级别控制能力。这是本设计的主要风险。缓解措施:
- 仅
/api/v1/system/*管理员接口可触发。 - 不提供任意命令执行接口。
- 不允许用户传入下载 URL。
- 不允许用户传入 compose 路径。
- 只升级本仓库 GitHub Release,且跳过 draft。
- 前端明确展示 Docker socket 权限提示。
测试策略
Go 单测
新增 system_upgrade_test.go 覆盖:
selectComposeAsset对 v4/v6 compose 内容的判断。.env中已有FLUX_VERSION时更新值。.env中缺少FLUX_VERSION时追加值。- 缺少部署目录、
.env、docker-compose.yml、Docker socket 时返回不可用原因。 - helper 命令构造固定命令序列,不拼接用户输入。
- 并发升级锁会拒绝第二个升级请求。
手动/集成验证
go-backend:go test ./...vite-frontend:pnpm run build- 本地容器验证:启动 Compose 后检查设置页升级卡片可显示能力状态。
- 在无 Docker socket 的开发环境验证按钮禁用并显示原因。
回滚与恢复
自动升级失败时不做自动容器回滚。后端会保留:
.env.upgrade.bakdocker-compose.yml.upgrade.bak
人工恢复步骤由错误信息提示:进入部署目录,按需恢复备份文件,再执行 docker compose up -d backend frontend。
决策记录
本设计已确定采用 Docker socket 整套升级方案,不再保留二进制替换作为本次实现路径。Docker socket 的权限风险通过管理员限制、命令白名单和前端提示控制。