diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ace08c8..bde240473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## v1.2.2-preview.3 + +- 修复历史数据目录包含 root-only 备份文件时,在线更新无法创建数据目录 ZIP 的问题;仅备份命令以容器 root 身份读取挂载目录,业务 Backend 仍保持非 root 运行。 + ## v1.2.2-preview.2 - 在线更新页明确提示每次升级都会重新备份数据库,并校验镜像摘要、数据库迁移与服务健康状态。 diff --git a/VERSION b/VERSION index 3864d38eb..6e17302cd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.2.2-preview.2 +v1.2.2-preview.3 diff --git a/backend/internal/hostupdate/manager_ops.go b/backend/internal/hostupdate/manager_ops.go index 1f659f7cb..b056c3be3 100644 --- a/backend/internal/hostupdate/manager_ops.go +++ b/backend/internal/hostupdate/manager_ops.go @@ -301,7 +301,7 @@ func (m *Manager) createBackup(version string) (Backup, error) { if err != nil { return Backup{}, err } - if err := m.compose(m.composePath(), version, m.config.StepTimeout, dataEntry, "exec", "-T", "backend", "tar", "-C", "/data", "-cf", "-", "."); err != nil { + if err := m.compose(m.composePath(), version, m.config.StepTimeout, dataEntry, "exec", "-T", "--user", "root", "backend", "tar", "-C", "/data", "-cf", "-", "."); err != nil { return Backup{}, fmt.Errorf("备份数据目录:%w", err) } if err := archive.Close(); err != nil { diff --git a/backend/internal/hostupdate/manager_ops_test.go b/backend/internal/hostupdate/manager_ops_test.go index 4794482b0..dea114057 100644 --- a/backend/internal/hostupdate/manager_ops_test.go +++ b/backend/internal/hostupdate/manager_ops_test.go @@ -2,6 +2,7 @@ package hostupdate import ( "archive/zip" + "context" "crypto/sha256" "encoding/hex" "io" @@ -11,6 +12,18 @@ import ( "testing" ) +type recordingRunner struct { + calls [][]string +} + +func (r *recordingRunner) Run(_ context.Context, _ string, args, _ []string, stdout, _ io.Writer) error { + r.calls = append(r.calls, append([]string(nil), args...)) + if stdout != nil { + _, _ = io.WriteString(stdout, "backup-fixture") + } + return nil +} + func TestSetEnvValuePreservesOtherSettings(t *testing.T) { directory := t.TempDir() path := filepath.Join(directory, ".env") @@ -90,3 +103,29 @@ func TestCurrentVersionRejectsLatest(t *testing.T) { t.Fatal("latest tag was accepted") } } + +func TestCreateBackupReadsBackendDataAsRoot(t *testing.T) { + installDir := t.TempDir() + backupDir := filepath.Join(installDir, "backups") + if err := os.MkdirAll(backupDir, 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(installDir, ".env"), []byte("POSTGRES_USER=canvas\nPOSTGRES_DB=canvas\n"), 0o600); err != nil { + t.Fatal(err) + } + runner := &recordingRunner{} + manager := &Manager{ + config: Config{InstallDir: installDir, ComposeFile: "docker-compose.deploy.yml", EnvFile: ".env", BackupDir: backupDir}, + runner: runner, + } + if _, err := manager.createBackup("v1.2.2-preview.2"); err != nil { + t.Fatal(err) + } + for _, call := range runner.calls { + joined := strings.Join(call, " ") + if strings.Contains(joined, "exec -T --user root backend tar -C /data -cf - .") { + return + } + } + t.Fatalf("backend data backup did not use root: %#v", runner.calls) +}