mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
feat(update): 更新进度走 EventBus + SSE(穿越进程重启的通知通道)
## 为什么必须走事件总线(用户口径:为什么不走 eventbus)
升级是**长耗时且会让进程消失**的动作。此前进度只往 log.Printf 写,
WebUI 要看得自己轮询 /kernel/update —— 而轮询在重启瞬间必然断线,
断线那一刻恰恰是用户最需要知道"升到哪一步/失败了/已回滚"的时刻。
EventBus 是内核既有的发布订阅机制(同步投递、handler panic 隔离),
用它不需要新造一条通知通道。
## 三个新事件类型
update_check 检查结果(只读,不改动)
update_progress 升级步骤:verify/backup/replace/restart/rollback/done/failed
update_result 最终结果(含回滚轨迹)
## ★ 顺序约束:先发事件,再动作
升级尾部几步(replace → restart)会让进程消失。顺序一反,最后几步的
事件永远发不出去。所以每个 publish* 都紧贴在其描述的动作**之前**。
## 直接 import events,不搞接口垫层
第一版我写了 EventPublisher 接口 + map[string]interface{} payload 想
"避免依赖",那是自欺欺人:events 包**不依赖任何内部包**(已验证),
import 它不会成环;而垫层会把事件降级成无类型 map,丢掉编译期检查。
已回退该设计,改为直接传 *events.Bus。
## SDK 补 EventBus() getter
PluginSDK 原先只有 Publish/Subscribe,没有暴露总线对象。而长流程
(升级闭环)需要把总线本身传下去持续发事件 —— 只给 Publish 的话,
每个调用点都要手动转发一遍。
## SSE 通路(守卫判据逼出来的正确修法)
TestEveryPublishedEventHasAnOutwardPath 抓到三个新事件"有发布者但无对外
通路"——症状是「发了、没报错、客户端收不到」。修法不是加白名单豁免,
而是真的接进 SSE:三者都进 sseEventRing(不只是实时推送),
断线重连要能补到**最终结论**(update_result),否则用户重连后只看到
"开始升级"而没有结局。
## 调用点
· pluginmgr(kernel_update):p.sdk.EventBus() → DoUpgradeBus
· webui(/kernel/update):h.sdk.EventBus() → runKernelUpgrade(pkg, bus)
· CLI:bus=nil,退化成纯 printf(CLI 没有总线)
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
// Package kernelupgrade 实现**内核**的升级闭环:探测 / 校验 / 备份 / 原子替换 / 重启 / 回滚。
|
||||
// 内核升级闭环:探测 / 校验 / 备份 / 原子替换 / 重启 / 回滚。
|
||||
// 包级说明见 doc.go(update 子系统)。
|
||||
//
|
||||
// 抽成独立包而不是留在 cmd/homed-upgrade:内核侧的 kernel_update 工具
|
||||
// 与 CLI 必须走**同一套**逻辑。两份实现必然漂移,而这条路径上漂移的
|
||||
@ -22,6 +23,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/events"
|
||||
)
|
||||
|
||||
// binVersion 是 homed --version 的输出。
|
||||
@ -248,6 +251,17 @@ func FileSize(p string) int64 {
|
||||
// 每一步失败都往回退,且**回滚后必须能正常启动**——这是本函数的核心承诺:
|
||||
// 宁可升级失败,不可让机器起不来。
|
||||
func DoUpgrade(root, binPath, newDG, pluginsDir, dataDir string) error {
|
||||
return DoUpgradeBus(root, binPath, newDG, pluginsDir, dataDir, nil)
|
||||
}
|
||||
|
||||
// DoUpgradeBus 同 DoUpgrade,但把每个步骤发布到事件总线。
|
||||
//
|
||||
// ★ 为什么必须"先发事件再动作":升级的最后几步会让进程消失
|
||||
// (replace → restart)。顺序一反,最后几步的事件永远发不出去 ——
|
||||
// 而那时用户最需要知道"升级到哪一步了/失败了/已回滚"。
|
||||
//
|
||||
// bus 为 nil 时退化成纯 printf(CLI 场景没有总线)。
|
||||
func DoUpgradeBus(root, binPath, newDG, pluginsDir, dataDir string, bus *events.Bus) error {
|
||||
newBin := FindFile(root, "homed")
|
||||
if newBin == "" {
|
||||
return fmt.Errorf("包内无 homed")
|
||||
@ -258,6 +272,7 @@ func DoUpgrade(root, binPath, newDG, pluginsDir, dataDir string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("预检失败,新二进制不可执行: %w", err)
|
||||
}
|
||||
publishProgress(bus, "kernel", "", "verify", "新内核可执行,版本 "+nv.Version)
|
||||
fmt.Printf("\n== 预检通过 ==\n新内核 %s 可执行\n", nv.Version)
|
||||
|
||||
// ── 备份:只备本次会动的文件(不整目录拷贝,那是几百 MB)──
|
||||
@ -275,6 +290,7 @@ func DoUpgrade(root, binPath, newDG, pluginsDir, dataDir string) error {
|
||||
return fmt.Errorf("备份 %s: %w", t, err)
|
||||
}
|
||||
}
|
||||
publishProgress(bus, "kernel", "", "backup", fmt.Sprintf("已备份 %d 个文件", len(targets)))
|
||||
fmt.Printf("已备份 %d 个文件 → %s\n", len(targets), backup)
|
||||
|
||||
// ── 写待确认升级标记(在替换**之前**)──
|
||||
@ -304,6 +320,7 @@ func DoUpgrade(root, binPath, newDG, pluginsDir, dataDir string) error {
|
||||
}
|
||||
|
||||
rollback := func(cause error) error {
|
||||
publishProgress(bus, "kernel", "", "rollback", cause.Error())
|
||||
fmt.Printf("\n❌ %v\n== 开始回滚 ==\n", cause)
|
||||
if dataDir != "" {
|
||||
if cerr := ClearPendingUpgrade(dataDir); cerr != nil {
|
||||
@ -354,6 +371,7 @@ func DoUpgrade(root, binPath, newDG, pluginsDir, dataDir string) error {
|
||||
if err != nil || got.Version != nv.Version || got.Commit != nv.Commit {
|
||||
return rollback(fmt.Errorf("版本复核不符:期望 %s/%s,实际 %v", nv.Version, ShortCommit(nv.Commit), got))
|
||||
}
|
||||
publishResult(bus, "kernel", "", true, curVer.Version, got.Version, false, "")
|
||||
fmt.Printf("\n✅ 升级完成:%s (commit %s)\n", got.Version, ShortCommit(got.Commit))
|
||||
fmt.Printf(" 备份保留在 %s(确认稳定后可删)\n", backup)
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user