fix(deploy): 在线更新 - 在停服前校验更新器写入权限并修正 systemd 可写目录

This commit is contained in:
ddCat
2026-08-31 06:55:14 +08:00
parent 9e718fcf4a
commit b23c839068
3 changed files with 46 additions and 1 deletions

View File

@@ -123,6 +123,14 @@ func (m *Manager) preflight(composePath, targetVersion string) error {
if _, err := os.Stat(m.envPath()); err != nil {
return fmt.Errorf("读取部署环境:%w", err)
}
if m.config.SelfUpdate {
if strings.TrimSpace(m.config.BinaryPath) == "" {
return errors.New("检查 Host Updater 安装目录:二进制路径为空")
}
if err := checkWritableDirectory(filepath.Dir(m.config.BinaryPath)); err != nil {
return fmt.Errorf("检查 Host Updater 安装目录:%w", err)
}
}
var disk syscall.Statfs_t
if err := syscall.Statfs(m.config.BackupDir, &disk); err != nil {
return fmt.Errorf("检查备份磁盘空间:%w", err)
@@ -144,6 +152,26 @@ func (m *Manager) preflight(composePath, targetVersion string) error {
return nil
}
func checkWritableDirectory(directory string) error {
directory = strings.TrimSpace(directory)
if directory == "" {
return errors.New("Host Updater 二进制路径为空")
}
temporary, err := os.CreateTemp(directory, ".updater-write-test-*")
if err != nil {
return err
}
name := temporary.Name()
if closeErr := temporary.Close(); closeErr != nil {
_ = os.Remove(name)
return closeErr
}
if removeErr := os.Remove(name); removeErr != nil {
return removeErr
}
return nil
}
func (m *Manager) prepareUpdaterBinary(targetVersion string) (string, error) {
if !m.config.SelfUpdate {
return "", nil

View File

@@ -129,3 +129,20 @@ func TestCreateBackupReadsBackendDataAsRoot(t *testing.T) {
}
t.Fatalf("backend data backup did not use root: %#v", runner.calls)
}
func TestCheckWritableDirectory(t *testing.T) {
directory := t.TempDir()
if err := checkWritableDirectory(directory); err != nil {
t.Fatalf("writable directory rejected: %v", err)
}
entries, err := os.ReadDir(directory)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Fatalf("write probe was not cleaned up: %v", entries)
}
if err := checkWritableDirectory(filepath.Join(directory, "missing")); err == nil {
t.Fatal("missing directory was accepted")
}
}

View File

@@ -97,7 +97,7 @@ install_service() {
'PrivateTmp=true' \
'ProtectHome=true' \
'ProtectSystem=full' \
"ReadWritePaths=${INSTALL_DIR} /var/lib/open-ai-canvas-updater ${SOCKET_DIR} ${UPDATER_BIN}" \
"ReadWritePaths=${INSTALL_DIR} /var/lib/open-ai-canvas-updater ${SOCKET_DIR} /usr/local/bin" \
'' \
'[Install]' \
'WantedBy=multi-user.target' > "$temporary_service"