fix(update): 统一 dataDir 推导,三端共用 update.ResolveDataDir

范围说明:本 commit 与 H1 属同一缺陷的另一面。Apply 现在会拒绝空
dataDir,而三端各自的推导逻辑此前会**真的返回空**(或写死生产路径),
不一起改则 H1 会把所有升级都拒掉、或让 pending 落到内核读不到的地方。

三处原来的推导:
  · cmd/homed-upgrade/main.go     env → 写死 /home/newqqagent
  · pluginmgr/kernel_update.go    env → Dir(pluginDir)
  · webui/kernel_update.go        env → 空(由调用方报错)

问题:① 硬编码 /home/newqqagent 在目录/用户迁移后指向不存在的路径;
     ② 从 pluginDir 反推与 webui 侧不一致,同一台机器两条升级通道算出
        两个 dataDir ⇒ 标记落在 A、第 0 层 update 从 B 读 ⇒ 升级启动
        失败时静默不回滚(症状是崩溃循环,极难反查到标记)。

现在三端统一调用 update.ResolveDataDir(exePath):
  ① HOMEAGENT_DATA_DIR(部署方显式声明,唯一可靠来源)
  ② <exe 所在目录>/data 兜底(替代原先的硬编码生产路径)
并删掉 cmd/homed-upgrade 里写死的 /home/newqqagent。

本 commit 可单独回退:回退后仅剩「各端自己推导」,与 H1 组合会退化为
「空 dataDir 一律拒绝升级」(安全侧失败,不会产生无回滚能力的升级)。
This commit is contained in:
JianFeeeee
2026-10-28 17:20:40 +08:00
parent ecde8dd515
commit 85fd38b7d1
3 changed files with 20 additions and 46 deletions

View File

@ -42,20 +42,17 @@ import (
)
// fail 打印错误并以非零退出(CLI 专属;库内部一律返回 error)。
// upgradeDataDir 推导数据目录(升级确认标记的落点)。
// upgradeDataDir 推导数据目录(升级确认标记 / 备份的落点)。
//
// 与生产一致:内核按 `--data <dir>` 运行,标记必须落在同一个 dataDir 下,
// 否则 update 模块读不到它。推导规则:
// 否则 update 模块读不到它。
//
// ① 环境变量 HOMEAGENT_DATA_DIR(部署方显式指定)
// ② 二进制同级的 ../newqqagent(/usr/local/bin/homed → /home/newqqagent)
// —— 太脆弱,改为最常见的部署形态:/usr/local/bin/homed → /home/newqqagent
// ③ 退回 /home/newqqagent(本仓默认部署路径)
// ★ 三端共用 update.ResolveDataDir(CLI / WebUI / agent 工具),
// 不再在这里写死 /home/newqqagent:硬编码在生产目录迁移后会让标记和备份
// 落到一个**不存在的路径**,而第 0 层 update 从真实的 --data 读 ⇒ 永远
// 读不到 ⇒ 升级失败时静默不回滚(2026-10-26 迁移教训)。
func upgradeDataDir(binPath string) string {
if d := strings.TrimSpace(os.Getenv("HOMEAGENT_DATA_DIR")); d != "" {
return d
}
return "/home/newqqagent"
return update.ResolveDataDir(binPath)
}
func fail(format string, a ...interface{}) {

View File

@ -98,7 +98,7 @@ func (p *Plugin) kernelUpdateApply(pkgPath string, release bool) (interface{}, e
bus = p.sdk.EventBus()
}
out, err := update.Apply(root, kernelBinPath(p), newDG, kernelDataDir(p), bus)
out, err := update.Apply(root, kernelBinPath(p), newDG, update.ResolveDataDir(kernelBinPath(p)), bus)
if err != nil {
return map[string]interface{}{"error": err.Error()}, nil
}
@ -140,36 +140,16 @@ func (p *Plugin) kernelUpdateApply(pkgPath string, release bool) (interface{}, e
return res, nil
}
// kernelDataDir 推导数据目录(升级确认标记的落点)。
// kernelDataDir 推导数据目录(升级确认标记 / 备份的落点)。
//
// 从 pluginDir 反推:<dataDir>/plugins ⇒ 上一级即 dataDir。
// 与内核 --data 的值一致,update 模块(第 0 层)才能读到同一个标记。
// ★ 三端共用 update.ResolveDataDir:CLI、WebUI、agent 工具必须算出**同一个**
// 路径,否则 pending 标记落在哪、第 0 层 update 就从哪读,两边一漂移,
// 升级启动失败时就静默不回滚(本文件此前反推 pluginDir 的做法就是这么
// 与 webui 侧走散的)。
func kernelDataDir(p *Plugin) string {
// <dataDir>/plugins ⇒ 上一级即 dataDir。
//
// ★ 只 Dir 一次。此前写成 Dir(Dir(...)),把 /home/newqqagent/plugins
// 算成 /home —— pending 标记落到 /home/kernel-upgrade-pending.json,
// 而第 0 层 update 模块从 ks.DataDir 读 ⇒ **永远读不到标记** ⇒
// agent 发起的升级一旦启动失败就**不会回滚**(静默失效)。
// ★ 不再写死生产路径(2026-10-26 迁移教训):
// 硬编码 /home/newqqagent 在目录/用户迁移后会让 pending 标记落到一个
// **已经不存在的目录**,而第 0 层 update 从 ks.DataDir 读 ⇒ 永远读不到
// ⇒ agent 发起的升级失败时静默不回滚。
//
// 优先级:显式环境变量(部署方声明自己在哪)→ 从 pluginDir 反推 → 空。
// 空表示"不知道",由调用方拒绝执行并报错,而不是猜一个路径。
if d := os.Getenv(dataDirEnv); d != "" {
return d
}
if p.pluginDir != "" {
return filepath.Dir(p.pluginDir)
}
return ""
return update.ResolveDataDir(kernelBinPath(p))
}
// dataDirEnv 是部署方声明数据目录的环境变量(与 webui 侧同名)。
const dataDirEnv = "HOMEAGENT_DATA_DIR"
// currentKernelVersion 读本机 homed 自报版本(尽力而为)。
func currentKernelVersion(p *Plugin) *update.BinVersion {
v, err := update.ReadBinVersion(kernelBinPath(p))

View File

@ -140,15 +140,12 @@ func currentKernelVersion() *update.BinVersion {
return v
}
// dataDirOf 推导数据目录(升级确认标记的落点)。
// dataDirOf 推导数据目录(升级确认标记 / 备份的落点)。
//
// 与内核 `--data` 必须一致,否则第 0 层 update 模块读不到 pending 标记。
// 优先用环境变量显式指定(部署方知道自己在哪),否则退回本机生产路径。
// ★ 三端共用 update.ResolveDataDir:pending 标记落在哪,第 0 层 update
// 模块就从哪读。两边一漂移,升级启动失败时就**静默不回滚**。
// 返回空的情况已不存在(ResolveDataDir 有 exe 同级 data 兜底),
// 但仍不写死任何生产绝对路径:迁移后它指向不存在的目录(静默不回滚)。
func dataDirOf(binPath string) string {
if d := os.Getenv("HOMEAGENT_DATA_DIR"); d != "" {
return d
}
// ★ 不写死生产路径:迁移后它会指向一个不存在的目录,
// 让升级 pending 标记落错地方(静默不回滚)。返回空由调用方报错。
return ""
return update.ResolveDataDir(binPath)
}