From 4e948f65d1378038044f311dc1511a9f3f30e0ac Mon Sep 17 00:00:00 2001 From: mrlee <470349233@qq.com> Date: Mon, 28 Sep 2026 11:24:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(updater):=20=E5=A4=8D=E7=94=A8=E5=8E=9F=20C?= =?UTF-8?q?ompose=20=E9=A1=B9=E7=9B=AE=E5=90=8D=E5=B9=B6=E4=BF=9D=E6=8A=A4?= =?UTF-8?q?=E9=9D=9E=20Compose=20=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ cmd/fonu-updater/main.go | 1 + docker-compose.hub.yml | 1 + docker-compose.yml | 1 + docs/system-upgrade.md | 6 ++++++ internal/updater/runner.go | 4 ++-- internal/updater/server.go | 25 ++++++++++++++++++++++++- internal/updater/server_test.go | 3 ++- 8 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cbd26f..043e705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. - **示例页面新增独立升级验证区块**:页面增加「本次升级验证」高亮区块,显示 `v1.3.7`,验证标识更新为 `v1.3.7-example-page`,用于直观确认升级后的前端资源已切换 +### 修复 + +- **复用原 Compose 项目名重建容器**:updater 在通过 helper 容器执行 `docker compose` 时,先从现有 `fonu` 容器读取 `com.docker.compose.project` 标签,并显式传入同一个项目名,避免 Compose 将现有容器视为另一个项目后重新创建网络、数据卷并撞上 `/fonu` 容器名冲突。现有容器不是 Compose 管理时,updater 会拒绝升级并提示按 Compose 方式重新部署,避免误建空数据卷 + ### 变更 - 版本号提升到 1.3.7;`web/package.json` 与 `web/package-lock.json` 同步对齐 diff --git a/cmd/fonu-updater/main.go b/cmd/fonu-updater/main.go index 907e08d..af84490 100644 --- a/cmd/fonu-updater/main.go +++ b/cmd/fonu-updater/main.go @@ -21,6 +21,7 @@ func main() { Branch: envOr("FONU_UPDATE_BRANCH", "main"), AppURL: envOr("FONU_UPDATE_APP_URL", "http://fonu:6893"), HostProjectDir: envOr("FONU_UPDATE_HOST_DIR", ""), + ProjectName: envOr("FONU_UPDATE_PROJECT_NAME", ""), SelfImage: envOr("FONU_UPDATE_SELF_IMAGE", ""), DockerSocket: envOr("FONU_UPDATE_DOCKER_SOCK", "/var/run/docker.sock"), }, nil) diff --git a/docker-compose.hub.yml b/docker-compose.hub.yml index 36bea38..712b8c5 100644 --- a/docker-compose.hub.yml +++ b/docker-compose.hub.yml @@ -82,6 +82,7 @@ services: FONU_UPDATE_BRANCH: main FONU_UPDATE_APP_URL: http://fonu:6893 FONU_UPDATE_HOST_DIR: ${FONU_UPDATE_HOST_DIR:-} + FONU_UPDATE_PROJECT_NAME: ${FONU_UPDATE_PROJECT_NAME:-fonu} FONU_UPDATE_SELF_IMAGE: fonu-updater:${FONU_VERSION:-local} FONU_UPDATE_DOCKER_SOCK: ${DOCKER_SOCK:-/var/run/docker.sock} TZ: ${TZ:-Asia/Shanghai} diff --git a/docker-compose.yml b/docker-compose.yml index 9b241de..5d2ffd0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,6 +64,7 @@ services: FONU_UPDATE_MODE: local FONU_UPDATE_APP_URL: http://fonu:6893 FONU_UPDATE_HOST_DIR: ${FONU_UPDATE_HOST_DIR:-} + FONU_UPDATE_PROJECT_NAME: ${FONU_UPDATE_PROJECT_NAME:-fonu} FONU_UPDATE_SELF_IMAGE: fonu-updater:${FONU_VERSION:-local} FONU_UPDATE_DOCKER_SOCK: ${DOCKER_SOCK:-/var/run/docker.sock} TZ: ${TZ:-Asia/Shanghai} diff --git a/docs/system-upgrade.md b/docs/system-upgrade.md index 18acdcb..55876a7 100644 --- a/docs/system-upgrade.md +++ b/docs/system-upgrade.md @@ -182,6 +182,12 @@ docker compose -f /实际/的/Compose/目录/docker-compose.hub.yml up -d --buil 状态接口会明确提示 Compose 文件缺失,修复挂载或设置覆盖后按钮才会重新显示。 +### 报 `container name "/fonu" is already in use` + +这表示 Compose 没有识别到当前容器所属的原项目,可能把升级当成新项目并尝试新建网络与数据卷。v1.3.7 的更新版 updater 会读取现有容器的 `com.docker.compose.project` 标签,并显式复用原项目名。 + +如果现有 `fonu` 容器不是由 Compose 管理,updater 会拒绝升级,避免新建 `fonu_fonu-data` 等空数据卷。应先按 Compose 方式重新部署,再使用一键升级。 + ### `docker-compose.hub.yml` 模式无法读取远端版本 检查 sidecar 容器内是否可以访问 Git 仓库: diff --git a/internal/updater/runner.go b/internal/updater/runner.go index c9fa87e..e1a5791 100644 --- a/internal/updater/runner.go +++ b/internal/updater/runner.go @@ -323,7 +323,7 @@ func (s *Server) compose(ctx context.Context, version string, args ...string) (s if plan.helper { return s.composeViaHelper(ctx, plan, version, args...) } - fullArgs := append([]string{"compose", "-f", plan.containerCompose}, args...) + fullArgs := append([]string{"compose", "--project-name", plan.projectName, "-f", plan.containerCompose}, args...) env := []string{"FONU_VERSION=" + version} return runCommand(ctx, s.cfg.ProjectDir, env, "docker", fullArgs...) } @@ -340,7 +340,7 @@ func (s *Server) composeViaHelper(ctx context.Context, plan composePlan, version "-w", "/workspace", "-e", "FONU_VERSION=" + version, image, - "compose", "-f", plan.helperComposePath, + "compose", "--project-name", plan.projectName, "-f", plan.helperComposePath, } helperArgs = append(helperArgs, args...) return runCommand(ctx, "", nil, "docker", helperArgs...) diff --git a/internal/updater/server.go b/internal/updater/server.go index 24ee02d..514a53f 100644 --- a/internal/updater/server.go +++ b/internal/updater/server.go @@ -31,6 +31,7 @@ type Config struct { Branch string AppURL string HostProjectDir string + ProjectName string SelfImage string DockerSocket string BuildTimeout time.Duration @@ -49,6 +50,7 @@ type Server struct { type composePlan struct { helper bool + projectName string containerCompose string hostProjectDir string hostComposePath string @@ -83,6 +85,7 @@ func NewServer(cfg Config, logger *slog.Logger) (*Server, error) { cfg.DockerSocket = "/var/run/docker.sock" } cfg.HostProjectDir = strings.TrimSpace(cfg.HostProjectDir) + cfg.ProjectName = strings.TrimSpace(cfg.ProjectName) cfg.SelfImage = strings.TrimSpace(cfg.SelfImage) if cfg.BuildTimeout <= 0 { cfg.BuildTimeout = 30 * time.Minute @@ -184,9 +187,13 @@ func (s *Server) ensureComposeFile(ctx context.Context) error { } func (s *Server) resolveComposePlan(ctx context.Context) (composePlan, error) { + projectName, err := s.composeProjectName(ctx) + if err != nil { + return composePlan{}, err + } info, err := os.Stat(s.composePath) if err == nil && !info.IsDir() { - return composePlan{containerCompose: s.composePath}, nil + return composePlan{projectName: projectName, containerCompose: s.composePath}, nil } if err == nil && info.IsDir() { return composePlan{}, fmt.Errorf("Compose 路径是目录而不是文件:%s", s.composePath) @@ -197,6 +204,7 @@ func (s *Server) resolveComposePlan(ctx context.Context) (composePlan, error) { if pathErr == nil { return composePlan{ helper: true, + projectName: projectName, hostProjectDir: hostDir, hostComposePath: hostComposePath, helperComposePath: helperPath, @@ -206,6 +214,21 @@ func (s *Server) resolveComposePlan(ctx context.Context) (composePlan, error) { return composePlan{}, fmt.Errorf("找不到 Compose 文件 %s,且无法自动定位宿主机目录;请设置 FONU_UPDATE_HOST_DIR", s.composePath) } +func (s *Server) composeProjectName(ctx context.Context) (string, error) { + projectName, err := s.dockerLabel(ctx, "com.docker.compose.project") + projectName = strings.TrimSpace(projectName) + if projectName != "" { + return projectName, nil + } + if s.cfg.ProjectName != "" { + return s.cfg.ProjectName, nil + } + if err != nil { + return "", fmt.Errorf("无法读取现有容器的 Compose 项目标签:%w", err) + } + return "", fmt.Errorf("现有 fonu 容器不是由 Compose 管理,无法安全一键升级;请按 Compose 方式重新部署") +} + func (s *Server) discoverHostCompose(ctx context.Context) (string, string, error) { if s.cfg.HostProjectDir != "" { if !filepath.IsAbs(s.cfg.HostProjectDir) { diff --git a/internal/updater/server_test.go b/internal/updater/server_test.go index eb83486..c7b7c4a 100644 --- a/internal/updater/server_test.go +++ b/internal/updater/server_test.go @@ -46,7 +46,7 @@ func TestNewServerDefaultsAndValidation(t *testing.T) { func TestEnsureComposeFile(t *testing.T) { dir := t.TempDir() - server, err := NewServer(Config{ProjectDir: dir, ComposeFile: "docker-compose.yml"}, nil) + server, err := NewServer(Config{ProjectDir: dir, ComposeFile: "docker-compose.yml", ProjectName: "fonu"}, nil) if err != nil { t.Fatal(err) } @@ -69,6 +69,7 @@ func TestResolveComposePlanUsesHostProjectDir(t *testing.T) { ProjectDir: containerDir, ComposeFile: "docker-compose.hub.yml", HostProjectDir: hostDir, + ProjectName: "fonu", }, nil) if err != nil { t.Fatal(err)