From 22f8d25d26b36af2025cfba62840678158782cef Mon Sep 17 00:00:00 2001 From: ddcat666 <26564566+ddcat666@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:26:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E4=BF=AE=E5=A4=8D=20Host=20Upda?= =?UTF-8?q?ter=20Compose=20=E9=A1=B9=E7=9B=AE=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish-images.yml | 24 +++++---- backend/internal/hostupdate/manager_ops.go | 52 +++++++++++++++++-- .../internal/hostupdate/manager_ops_test.go | 40 ++++++++++++++ 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-images.yml b/.github/workflows/publish-images.yml index 8139c30fb..ecc3ff188 100644 --- a/.github/workflows/publish-images.yml +++ b/.github/workflows/publish-images.yml @@ -207,12 +207,18 @@ jobs: if [[ "$GITHUB_REF_NAME" == *-* || "$GITHUB_REF_NAME" == *.rc* || "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then prerelease_args+=(--prerelease) fi - gh release create "$GITHUB_REF_NAME" \ - dist/open-ai-canvas-host-updater-linux-amd64 \ - dist/open-ai-canvas-host-updater-linux-arm64 \ - dist/SHA256SUMS \ - docker-compose.deploy.yml \ - --verify-tag \ - --title "$GITHUB_REF_NAME" \ - --notes-file dist/RELEASE_NOTES.md \ - "${prerelease_args[@]}" + assets=( + dist/open-ai-canvas-host-updater-linux-amd64 + dist/open-ai-canvas-host-updater-linux-arm64 + dist/SHA256SUMS + docker-compose.deploy.yml + ) + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber + else + gh release create "$GITHUB_REF_NAME" "${assets[@]}" \ + --verify-tag \ + --title "$GITHUB_REF_NAME" \ + --notes-file dist/RELEASE_NOTES.md \ + "${prerelease_args[@]}" + fi diff --git a/backend/internal/hostupdate/manager_ops.go b/backend/internal/hostupdate/manager_ops.go index 4f87c2d2b..9f3ef34ef 100644 --- a/backend/internal/hostupdate/manager_ops.go +++ b/backend/internal/hostupdate/manager_ops.go @@ -264,16 +264,22 @@ func (m *Manager) compose(composePath, imageTag string, timeout time.Duration, s func (m *Manager) composeWithImages(composePath, imageTag string, images deploymentImages, timeout time.Duration, stdout io.Writer, arguments ...string) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - args := []string{"compose", "--env-file", m.envPath(), "-f", composePath} + envFile := m.envPath() + if images.backend != "" { + var err error + envFile, err = m.writeComposeEnvOverride(images) + if err != nil { + return fmt.Errorf("准备目标 Compose 环境:%w", err) + } + defer os.Remove(envFile) + } + args := []string{"compose", "--project-name", composeProjectName(m.config.InstallDir), "--env-file", envFile, "-f", composePath} args = append(args, arguments...) var stderr bytes.Buffer if stdout == nil { stdout = io.Discard } environment := []string{"CANVAS_IMAGE_TAG=" + strings.TrimPrefix(imageTag, "v")} - if images.backend != "" { - environment = append(environment, "CANVAS_BACKEND_IMAGE="+images.backend, "CANVAS_WEB_IMAGE="+images.web) - } err := m.runner.Run(ctx, "docker", args, environment, stdout, &stderr) if err != nil { message := strings.TrimSpace(stderr.String()) @@ -288,6 +294,44 @@ func (m *Manager) composeWithImages(composePath, imageTag string, images deploym return nil } +func composeProjectName(installDir string) string { + name := filepath.Base(filepath.Clean(installDir)) + if name == "." || name == string(filepath.Separator) || name == "" { + return "open-ai-canvas" + } + var builder strings.Builder + for _, char := range strings.ToLower(name) { + if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '-' || char == '_' { + builder.WriteRune(char) + } + } + if result := strings.Trim(builder.String(), "-_ "); result != "" { + return result + } + return "open-ai-canvas" +} + +func (m *Manager) writeComposeEnvOverride(images deploymentImages) (string, error) { + data, err := os.ReadFile(m.envPath()) + if err != nil { + return "", err + } + path := filepath.Join(m.config.StateDir, ".compose-env-override-"+randomID()) + if err := os.WriteFile(path, data, 0o600); err != nil { + return "", err + } + cleanup := func() { _ = os.Remove(path) } + if err := setEnvValue(path, "CANVAS_BACKEND_IMAGE", images.backend); err != nil { + cleanup() + return "", err + } + if err := setEnvValue(path, "CANVAS_WEB_IMAGE", images.web); err != nil { + cleanup() + return "", err + } + return path, nil +} + func (m *Manager) verifyImages(targetVersion string) (deploymentImages, error) { images := immutableImageRefs(m.config.Repository, targetVersion) refs := []string{images.backend, images.web} diff --git a/backend/internal/hostupdate/manager_ops_test.go b/backend/internal/hostupdate/manager_ops_test.go index c9add7a16..6b2f7ee9c 100644 --- a/backend/internal/hostupdate/manager_ops_test.go +++ b/backend/internal/hostupdate/manager_ops_test.go @@ -146,3 +146,43 @@ func TestCheckWritableDirectory(t *testing.T) { t.Fatal("missing directory was accepted") } } + +func TestComposeProjectNameUsesInstallDirectory(t *testing.T) { + cases := map[string]string{ + "/opt/yingce": "yingce", + "/srv/Open AI Canvas_2": "openaicanvas_2", + "/": "open-ai-canvas", + "/srv/!!!": "open-ai-canvas", + } + for installDir, want := range cases { + if got := composeProjectName(installDir); got != want { + t.Errorf("composeProjectName(%q) = %q, want %q", installDir, got, want) + } + } +} + +func TestWriteComposeEnvOverrideReplacesImageRefs(t *testing.T) { + installDir := t.TempDir() + stateDir := filepath.Join(installDir, "state") + if err := os.MkdirAll(stateDir, 0o700); err != nil { + t.Fatal(err) + } + envPath := filepath.Join(installDir, ".env") + if err := os.WriteFile(envPath, []byte("CANVAS_BACKEND_IMAGE=old-backend\nCANVAS_WEB_IMAGE=old-web\nPOSTGRES_DB=canvas\n"), 0o600); err != nil { + t.Fatal(err) + } + manager := &Manager{config: Config{InstallDir: installDir, EnvFile: ".env", StateDir: stateDir}} + path, err := manager.writeComposeEnvOverride(deploymentImages{backend: "new-backend", web: "new-web"}) + if err != nil { + t.Fatal(err) + } + defer os.Remove(path) + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + value := string(data) + if !strings.Contains(value, "CANVAS_BACKEND_IMAGE=new-backend\n") || !strings.Contains(value, "CANVAS_WEB_IMAGE=new-web\n") || !strings.Contains(value, "POSTGRES_DB=canvas\n") { + t.Fatalf("unexpected override env: %q", value) + } +}