From a8ecb8257c56283a6a3d04bcda0dcebc0a971535 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Wed, 28 Oct 2026 17:51:16 +0800 Subject: [PATCH] =?UTF-8?q?revert:=20=E5=9B=9E=E9=80=80=2085fd38b=EF=BC=88?= =?UTF-8?q?ResolveDataDir=20=E4=BB=8E=20exe=20=E7=8C=9C=20dataDir=EF=BC=8C?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=E5=BF=85=E6=8C=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 85fd38b7d14e07ff34d7998f9c95f42ca30964a7. ## 为什么回退 85fd38b 把三端 dataDir 推导统一成 update.ResolveDataDir(exePath): `HOMEAGENT_DATA_DIR` → `/data` 兜底。方向(三端必须算出 同一个值)是对的,但**兜底那一项在生产上是错的**: ExecStart=/usr/bin/homed --role=guard --data /var/lib/hologram ResolveDataDir("/usr/bin/homed") == "/usr/bin/data" ← 与真实值不符 `HOMEAGENT_DATA_DIR` 在现有部署里并没有设置,所以实际生效的就是这个兜底。 后果两个,都是致命的: ① Apply 第一步要在 dataDir 下建备份目录,而 /usr/bin 在 ProtectSystem=strict 下只读 ⇒ EROFS ⇒ 升级必挂 ② pending 标记落到内核读不到的位置 ⇒ 升级启动失败后静默不回滚 ## 附带问题:空值守卫变成死代码 ecde8dd 的 H1(P1)加的是「dataDir 为空就拒绝升级」。85fd38b 的 ResolveDataDir 兜底**永不为空**(exePath 为空时还会自己去取 KernelBinaryPath),于是三个调用点传进来的 dataDir 恒非空 —— 守卫还在,但守的是一个永远非空的常量,实际不会再拦任何东西。 「不知道」伪装成「知道」比没有守卫更危险:守卫看起来在守,出事时才发现 它从来没生效。所以判据 P1 在 85fd38b 之上是咬不住的。 ## 回退范围 只回退 85fd38b 的三个文件(CLI / pluginmgr / webui 的推导逻辑), 回到 ecde8dd 的状态;不影响 ecde8dd 本身。回退后: · ResolveDataDir 及其所有调用点消失,无悬空引用 · go build ./... 通过 · 三端暂时回到「各端自己推导」——这正是紧随其后的下一个 commit 要替换掉的形态(改为取内核权威源,不做任何推导) 回退 + 后续修复合起来才完整:单看本 commit,dataDir 解析又散回三处; 下一个 commit 用「注入内核真实 -data」取代全部推导。 --- cmd/homed-upgrade/main.go | 17 ++++++----- internal/plugins/pluginmgr/kernel_update.go | 34 ++++++++++++++++----- internal/plugins/webui/kernel_update.go | 15 +++++---- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/cmd/homed-upgrade/main.go b/cmd/homed-upgrade/main.go index 72c5afc1..dad9ece1 100644 --- a/cmd/homed-upgrade/main.go +++ b/cmd/homed-upgrade/main.go @@ -42,17 +42,20 @@ import ( ) // fail 打印错误并以非零退出(CLI 专属;库内部一律返回 error)。 -// upgradeDataDir 推导数据目录(升级确认标记 / 备份的落点)。 +// upgradeDataDir 推导数据目录(升级确认标记的落点)。 // // 与生产一致:内核按 `--data ` 运行,标记必须落在同一个 dataDir 下, -// 否则 update 模块读不到它。 +// 否则 update 模块读不到它。推导规则: // -// ★ 三端共用 update.ResolveDataDir(CLI / WebUI / agent 工具), -// 不再在这里写死 /home/newqqagent:硬编码在生产目录迁移后会让标记和备份 -// 落到一个**不存在的路径**,而第 0 层 update 从真实的 --data 读 ⇒ 永远 -// 读不到 ⇒ 升级失败时静默不回滚(2026-10-26 迁移教训)。 +// ① 环境变量 HOMEAGENT_DATA_DIR(部署方显式指定) +// ② 二进制同级的 ../newqqagent(/usr/local/bin/homed → /home/newqqagent) +// —— 太脆弱,改为最常见的部署形态:/usr/local/bin/homed → /home/newqqagent +// ③ 退回 /home/newqqagent(本仓默认部署路径) func upgradeDataDir(binPath string) string { - return update.ResolveDataDir(binPath) + if d := strings.TrimSpace(os.Getenv("HOMEAGENT_DATA_DIR")); d != "" { + return d + } + return "/home/newqqagent" } func fail(format string, a ...interface{}) { diff --git a/internal/plugins/pluginmgr/kernel_update.go b/internal/plugins/pluginmgr/kernel_update.go index 8b3507bc..ff9bc1cb 100644 --- a/internal/plugins/pluginmgr/kernel_update.go +++ b/internal/plugins/pluginmgr/kernel_update.go @@ -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, update.ResolveDataDir(kernelBinPath(p)), bus) + out, err := update.Apply(root, kernelBinPath(p), newDG, kernelDataDir(p), bus) if err != nil { return map[string]interface{}{"error": err.Error()}, nil } @@ -140,16 +140,36 @@ func (p *Plugin) kernelUpdateApply(pkgPath string, release bool) (interface{}, e return res, nil } -// kernelDataDir 推导数据目录(升级确认标记 / 备份的落点)。 +// kernelDataDir 推导数据目录(升级确认标记的落点)。 // -// ★ 三端共用 update.ResolveDataDir:CLI、WebUI、agent 工具必须算出**同一个** -// 路径,否则 pending 标记落在哪、第 0 层 update 就从哪读,两边一漂移, -// 升级启动失败时就静默不回滚(本文件此前反推 pluginDir 的做法就是这么 -// 与 webui 侧走散的)。 +// 从 pluginDir 反推:/plugins ⇒ 上一级即 dataDir。 +// 与内核 --data 的值一致,update 模块(第 0 层)才能读到同一个标记。 func kernelDataDir(p *Plugin) string { - return update.ResolveDataDir(kernelBinPath(p)) + // /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 "" } +// dataDirEnv 是部署方声明数据目录的环境变量(与 webui 侧同名)。 +const dataDirEnv = "HOMEAGENT_DATA_DIR" + // currentKernelVersion 读本机 homed 自报版本(尽力而为)。 func currentKernelVersion(p *Plugin) *update.BinVersion { v, err := update.ReadBinVersion(kernelBinPath(p)) diff --git a/internal/plugins/webui/kernel_update.go b/internal/plugins/webui/kernel_update.go index 44e7b40c..144a2f6a 100644 --- a/internal/plugins/webui/kernel_update.go +++ b/internal/plugins/webui/kernel_update.go @@ -140,12 +140,15 @@ func currentKernelVersion() *update.BinVersion { return v } -// dataDirOf 推导数据目录(升级确认标记 / 备份的落点)。 +// dataDirOf 推导数据目录(升级确认标记的落点)。 // -// ★ 三端共用 update.ResolveDataDir:pending 标记落在哪,第 0 层 update -// 模块就从哪读。两边一漂移,升级启动失败时就**静默不回滚**。 -// 返回空的情况已不存在(ResolveDataDir 有 exe 同级 data 兜底), -// 但仍不写死任何生产绝对路径:迁移后它指向不存在的目录(静默不回滚)。 +// 与内核 `--data` 必须一致,否则第 0 层 update 模块读不到 pending 标记。 +// 优先用环境变量显式指定(部署方知道自己在哪),否则退回本机生产路径。 func dataDirOf(binPath string) string { - return update.ResolveDataDir(binPath) + if d := os.Getenv("HOMEAGENT_DATA_DIR"); d != "" { + return d + } + // ★ 不写死生产路径:迁移后它会指向一个不存在的目录, + // 让升级 pending 标记落错地方(静默不回滚)。返回空由调用方报错。 + return "" }