Skip to content

Commit 9518713

Browse files
author
Thouen
committed
fix(update): 升降版本保留原有配置并隔离包管理进程
- preinstall 备份 data/ 与 config/(含 bbolt config.db),postinstall 在 systemd-run 独立单元内先停服再恢复后启动,升级/降级不再丢失采集通道等业务数据 - runPkgManager 经 systemd-run --wait 创建 transient unit 执行 dpkg/rpm,避免目标包 postinst 重启 edgeCore 时 CGroup 级联终止 dpkg(half-configured)
1 parent c1b6227 commit 9518713

5 files changed

Lines changed: 93 additions & 10 deletions

File tree

‎internal/update/local.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,9 @@ func (m *Manager) installPkg(path, format string) error {
276276
var err error
277277
switch format {
278278
case "deb":
279-
out, err = m.execFn("dpkg", "--force-confnew", "--force-confmiss", "-i", path).CombinedOutput()
279+
out, err = m.runPkgManager("dpkg", "--force-confnew", "--force-confmiss", "-i", path)
280280
case "rpm":
281-
out, err = m.execFn("rpm", "-Uvh", path).CombinedOutput()
281+
out, err = m.runPkgManager("rpm", "-Uvh", path)
282282
}
283283
if err != nil {
284284
return fmt.Errorf("%s 安装失败: %s: %s", format, strings.TrimSpace(string(out)), err)

‎internal/update/local_test.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"archive/tar"
55
"compress/gzip"
66
"os"
7+
"os/exec"
78
"path/filepath"
9+
"reflect"
810
"runtime"
911
"testing"
1012
)
@@ -192,3 +194,46 @@ func TestArchIncompatibleFilenameStillParsed(t *testing.T) {
192194
t.Fatal("incompatible package should carry a reason")
193195
}
194196
}
197+
198+
func TestRunPkgManagerWrapsWithSystemdRun(t *testing.T) {
199+
var calls [][]string
200+
m := NewManager("", "", nil)
201+
m.execFn = func(name string, arg ...string) *exec.Cmd {
202+
calls = append(calls, append([]string{name}, arg...))
203+
// 探测 systemd-run --version 与包装后的命令均返回成功
204+
return exec.Command("echo", "ok")
205+
}
206+
if _, err := m.runPkgManager("dpkg", "--force-confnew", "--force-confmiss", "-i", "/tmp/x.deb"); err != nil {
207+
t.Fatal(err)
208+
}
209+
if len(calls) != 2 {
210+
t.Fatalf("expected 2 calls, got %v", calls)
211+
}
212+
want := []string{"systemd-run", "--wait", "--quiet", "--slice=system.slice", "--", "dpkg", "--force-confnew", "--force-confmiss", "-i", "/tmp/x.deb"}
213+
if !reflect.DeepEqual(calls[1], want) {
214+
t.Fatalf("unexpected wrapped call: %v", calls[1])
215+
}
216+
}
217+
218+
func TestRunPkgManagerFallback(t *testing.T) {
219+
var calls [][]string
220+
m := NewManager("", "", nil)
221+
m.execFn = func(name string, arg ...string) *exec.Cmd {
222+
calls = append(calls, append([]string{name}, arg...))
223+
// systemd-run 不可用(探测失败),后续直接执行
224+
if name == "systemd-run" {
225+
return exec.Command("sh", "-c", "exit 127")
226+
}
227+
return exec.Command("echo", "ok")
228+
}
229+
if _, err := m.runPkgManager("rpm", "-Uvh", "/tmp/x.rpm"); err != nil {
230+
t.Fatal(err)
231+
}
232+
if len(calls) != 2 {
233+
t.Fatalf("expected 2 calls, got %v", calls)
234+
}
235+
want := []string{"rpm", "-Uvh", "/tmp/x.rpm"}
236+
if !reflect.DeepEqual(calls[1], want) {
237+
t.Fatalf("unexpected fallback call: %v", calls[1])
238+
}
239+
}

‎internal/update/manager.go‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ func (m *Manager) Status() State {
7878
return m.state
7979
}
8080

81+
// runPkgManager 在独立 systemd transient unit 中执行包管理器。Web UI 升级时
82+
// dpkg/rpm 由 edgeCore 进程派生、运行于 edgeCore.service 的 CGroup 内;目标包
83+
// postinst 的 systemctl restart 会连同 postinst/dpkg 一并终止(dpkg 卡
84+
// half-configured,升级误报失败并回滚)。
85+
// 不能用 --scope:scope 生命周期绑定调用进程,edgeCore 被 restart 杀死时
86+
// systemd 会清理 scope 内进程。改为 --wait 创建独立 transient service unit,
87+
// 生命周期由 systemd 管理、与调用者解耦,edgeCore 死后 dpkg 仍能完成安装。
88+
// 非 systemd 环境(无 systemd-run)回退直接执行。
89+
func (m *Manager) runPkgManager(args ...string) ([]byte, error) {
90+
if _, err := m.execFn("systemd-run", "--version").Output(); err == nil {
91+
wrapped := append([]string{"--wait", "--quiet", "--slice=system.slice", "--"}, args...)
92+
return m.execFn("systemd-run", wrapped...).CombinedOutput()
93+
}
94+
return m.execFn(args[0], args[1:]...).CombinedOutput()
95+
}
96+
8197
// Checker 暴露底层的版本检查器(供 handler 查询)。
8298
func (m *Manager) Checker() *Checker { return m.checker }
8399

‎scripts/postinstall.sh‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ restore_dir() {
2222
fi
2323
}
2424

25-
# 恢复 data 和 config
26-
restore_dir "$BACKUP_DIR/data" "$DATA_DIR"
27-
restore_dir "$BACKUP_DIR/config" "$CONFIG_DIR"
28-
2925
# 仅在 systemctl 存在时执行 systemd 操作
3026
if command -v systemctl >/dev/null 2>&1; then
3127
echo "[postinstall] Reloading systemd..."
@@ -34,10 +30,33 @@ if command -v systemctl >/dev/null 2>&1; then
3430
echo "[postinstall] Enabling service..."
3531
systemctl enable edgeCore || true
3632

37-
echo "[postinstall] Starting service..."
38-
systemctl restart edgeCore || true
33+
# 关键:通过 Web UI 升级时,dpkg/postinst 由 edgeCore 进程派生,
34+
# 运行于 edgeCore.service 的 CGroup 内。直接 systemctl restart 会连同
35+
# postinst/dpkg 一并终止(dpkg 状态卡在 half-configured,升级误报失败)。
36+
# 用 systemd-run --wait 创建独立 transient unit 执行 systemd 操作:
37+
# unit 生命周期由 systemd 管理、与调用者解耦(不能用 --scope)。
38+
if command -v systemd-run >/dev/null 2>&1; then
39+
# 先停止旧服务:旧进程持有 data/config.db、runtime.db 的 bbolt 文件锁,
40+
# 若在运行期间直接覆盖恢复文件会与旧进程写入竞态、导致数据库损坏。
41+
# 停止后旧进程退出、文件释放,恢复与启动都在稳定状态下进行。
42+
systemd-run --wait --quiet --slice=system.slice systemctl stop edgeCore || true
43+
44+
# 恢复 data 和 config(旧进程已退出,文件处于稳定状态)
45+
restore_dir "$BACKUP_DIR/data" "$DATA_DIR"
46+
restore_dir "$BACKUP_DIR/config" "$CONFIG_DIR"
47+
48+
# 启动新版本服务
49+
systemd-run --wait --quiet --slice=system.slice systemctl start edgeCore || true
50+
else
51+
# 无 systemd-run 环境:保持先恢复再重启的顺序,避免服务长时间停摆
52+
restore_dir "$BACKUP_DIR/data" "$DATA_DIR"
53+
restore_dir "$BACKUP_DIR/config" "$CONFIG_DIR"
54+
systemctl restart edgeCore || true
55+
fi
3956
else
40-
echo "[postinstall] systemctl not found, skipping systemd service operations."
57+
# 非 systemd 环境:无服务进程持有 bbolt 文件锁,直接恢复配置即可
58+
restore_dir "$BACKUP_DIR/data" "$DATA_DIR"
59+
restore_dir "$BACKUP_DIR/config" "$CONFIG_DIR"
4160
fi
4261

4362
echo "[postinstall] Completed."

‎scripts/preinstall.sh‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set -e
44

55
ROOT_DIR="/usr/local/bin/edgeCore"
66
CONFIG_DIR="$ROOT_DIR/config"
7+
DATA_DIR="$ROOT_DIR/data"
78
BACKUP_DIR="/tmp/edgeCore_backup"
89

910
echo "[preinstall] Starting preinstall script..."
@@ -27,7 +28,9 @@ backup_dir() {
2728
fi
2829
}
2930

30-
# 备份目录
31+
# 备份目录:升级/降级必须保留原有配置,否则通道/设备等业务数据
32+
# (位于 data/config.db,bbolt 存储)会随版本变更而丢失。
33+
backup_dir "$DATA_DIR" "$BACKUP_DIR/data"
3134
backup_dir "$CONFIG_DIR" "$BACKUP_DIR/config"
3235

3336
echo "[preinstall] Completed."

0 commit comments

Comments
 (0)