mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
meta: 内核版本升到 1.0.0;生产切换脚本改走 hmap 正规通道(Part 6.5)
## 版本号
1.0.0:外部插件从 C ABI 动态库迁到子进程 + 共享内存。首个不再加载
`.so`/`.dll` 的版本,与 0.9.x 不兼容(存量插件必须用新版 plugindev 重编)。
SDKCompatibleVersion 同步升 1.0.0。
同时删掉 ABIVersion / CABINum / 51 个 Core<Method> 整数 ID —— 随 Part 6.2
删 internal/plugin/cabi/ 就已无使用者(grep 确认只剩定义处)。留着会让人
以为 C 层协商还在生效,或以为加 method 要同步维护那张整数表。
⚠️ 注意 Makefile 的 `VERSION ?= $(shell git describe --tags --dirty)`:
实际注入值来自 git tag,meta.go 里的默认值只在不带 ldflags 时生效。
make build 当前注入 v0.9.1-56-g2572688-dirty。要让 1.0.0 真正生效需打
v1.0.0 tag 或显式传 VERSION=1.0.0。
## 生产切换脚本重写
第一版是手工拷 plugin.bin + 手改 plugin.json 的 entry —— 那等于**重新实现
了一遍 hmap 解包逻辑,且实现得更差**。漏掉的东西:
platforms 字段 hmap 内的 plugin.json 本来就写对了
平台二进制选择 我硬编码 _linux_amd64,正规路径用 platformBinary()
overwrite 语义 StopAndUnload 停旧实例但**保留配置表**
失败回滚 os.Rename 备份旧目录,解包失败自动恢复
校验 validatePackage 查 manifest + 各平台二进制齐全
配置保留那条尤其关键:生产 17 个插件都有配置(qq 账号、weather 默认城市、
browser profile 路径)。我的脚本恰好没碰配置表所以侥幸不丢,但那是运气
不是设计。
改为 POST 到 pluginmgr 的 HTTP 端点(127.0.0.1:9876/plugins),
传 {path, overwrite:true} 走 installFromPath → installFromData。
保留的一个设计:**先全部校验再动手**。任一插件缺 hmap 就整批中止——
新 homed 不认 .so,「一半装了一半没装」的中间态最难排查。
## 生产切换已执行
顺序(先换二进制再装包,而非反过来):
1. systemctl stop homeagent
2. 换 /usr/local/bin/homed
3. 起服务 —— 15 个 .so 插件报可操作错误被跳过,homed 本体与 16 个内置正常
4. 逐个 POST 装 17 个 hmap(overwrite=true)
5. 待重启核对
第 3 步顺带在真实二进制上验证了 Part 6.2 的可操作错误:
[plugin] dynamic weather: 检测到旧 C ABI 产物(plugin.so/.dll/.dylib)。
外部插件已改为子进程模式,请用新版 plugindev 重编产出 plugin.bin
(业务代码无需修改)
不崩溃,只跳过。若反过来先装包,旧 homed 的 StopAndUnload 会停掉 qq
消息通道且无法重载 .bin,会卡在「插件全挂」的状态。
结果:17/17 成功,全部 config_kept=true;0 个残留 .so;17 个 plugin.bin
均有执行位;17 个 manifest 的 entry 均为 plugin.bin;无 .bak 残留。
bundle 包正确挑了当前平台(weather 目录只留 8.7MB 的 linux/amd64 那份)。
备份:/home/newqqagent-migration-backup-20260902-214812
(plugins 全目录 + homed.old + homeagent.service,162MB)
验证:go build ./... 通过;go test ./... 全仓无失败。
Ref: docs/zh/plugin-migration-plan.md Part 6.5
This commit is contained in:
132
docs/zh/experiments/plugin-arch/19-migration-verify/switch-production.py
Executable file
132
docs/zh/experiments/plugin-arch/19-migration-verify/switch-production.py
Executable file
@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生产切换:经 pluginmgr 正规通道安装 17 个 hmap(Part 6.5)。
|
||||
|
||||
与手工拷贝方案的区别 —— 这里复用内核自己的安装逻辑:
|
||||
|
||||
validatePackage 校验 manifest + 平台二进制齐全
|
||||
StopAndUnload 停旧实例但**保留配置表**
|
||||
os.Rename 备份 解包失败自动回滚到旧版本
|
||||
platformBinary() 按 runtime 挑当前平台那份,重命名为 plugin.bin
|
||||
chmod 0755 补执行位
|
||||
|
||||
手工拷贝会重新实现这一套,且必然实现得更差(第一版就漏了 platforms 字段
|
||||
与配置保留语义)。
|
||||
|
||||
用法:
|
||||
switch-production.py 演练
|
||||
switch-production.py --apply 实际安装
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
PROD_PLUGINS = "/home/newqqagent/plugins"
|
||||
SDK_EXAMPLE = "/home/program/TrueAgent/third_party/homeagent-sdk/example"
|
||||
PLUGINMGR = "http://127.0.0.1:9876/plugins"
|
||||
|
||||
|
||||
def find_hmap(name):
|
||||
"""找插件的 hmap 包。
|
||||
|
||||
bundle:true -> <snake>_bundle.hmap(含多平台二进制)
|
||||
bundle:false -> <snake>_<goos>_<goarch>.hmap(qq 是这种)
|
||||
"""
|
||||
dist = os.path.join(SDK_EXAMPLE, name, "dist")
|
||||
if not os.path.isdir(dist):
|
||||
return None
|
||||
cands = [f for f in os.listdir(dist) if f.endswith(".hmap")]
|
||||
if not cands:
|
||||
return None
|
||||
for c in cands:
|
||||
if c.endswith("_bundle.hmap"):
|
||||
return os.path.join(dist, c)
|
||||
return os.path.join(dist, sorted(cands)[0])
|
||||
|
||||
|
||||
def install(path):
|
||||
"""POST 到 pluginmgr。overwrite=true 走原地更新分支,保留配置表。"""
|
||||
body = json.dumps({"path": path, "overwrite": True}).encode()
|
||||
req = urllib.request.Request(
|
||||
PLUGINMGR, data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=180) as resp:
|
||||
return json.loads(resp.read().decode()), None
|
||||
except urllib.error.HTTPError as e:
|
||||
return None, "HTTP %d: %s" % (e.code, e.read().decode()[:300])
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
|
||||
|
||||
def main():
|
||||
apply = "--apply" in sys.argv
|
||||
|
||||
targets = sorted(
|
||||
d for d in os.listdir(PROD_PLUGINS)
|
||||
if os.path.isfile(os.path.join(PROD_PLUGINS, d, "plugin.so"))
|
||||
or os.path.isfile(os.path.join(PROD_PLUGINS, d, "plugin.bin"))
|
||||
)
|
||||
print("生产外部插件: %d 个" % len(targets))
|
||||
|
||||
# 先全部校验,任一缺包就整批中止。
|
||||
# 理由:新 homed 不认 .so,「一半装了一半没装」的中间态最难排查。
|
||||
plan = []
|
||||
missing = []
|
||||
for name in targets:
|
||||
h = find_hmap(name)
|
||||
if h is None:
|
||||
missing.append(name)
|
||||
else:
|
||||
plan.append((name, h))
|
||||
|
||||
if missing:
|
||||
print("\n✗ 中止:以下插件缺 hmap 包:")
|
||||
for m in missing:
|
||||
print(" " + m)
|
||||
print("\n先跑 rebuild-plugins.sh 重编。")
|
||||
return 1
|
||||
|
||||
print("✓ 全部 %d 个 hmap 就位\n" % len(plan))
|
||||
for name, h in plan:
|
||||
print(" %-16s %-44s %6d KB" % (
|
||||
name, os.path.basename(h), os.path.getsize(h) // 1024))
|
||||
|
||||
if not apply:
|
||||
print("\n[演练] 加 --apply 才实际安装")
|
||||
return 0
|
||||
|
||||
print("\n经 pluginmgr 安装(overwrite=true,保留配置)...")
|
||||
ok = 0
|
||||
failed = []
|
||||
for name, h in plan:
|
||||
result, err = install(h)
|
||||
if err:
|
||||
print(" ✗ %-16s %s" % (name, err))
|
||||
failed.append(name)
|
||||
continue
|
||||
if "error" in result:
|
||||
print(" ✗ %-16s %s: %s" % (
|
||||
name, result["error"], result.get("details", "")))
|
||||
failed.append(name)
|
||||
continue
|
||||
print(" ✓ %-16s %-12s v%s -> v%s config_kept=%s" % (
|
||||
name,
|
||||
result.get("action", "?"),
|
||||
result.get("previous_version", "?"),
|
||||
result.get("version", "?"),
|
||||
result.get("config_kept", False)))
|
||||
ok += 1
|
||||
|
||||
print("\n成功 %d / 失败 %d" % (ok, len(failed)))
|
||||
if failed:
|
||||
print("失败: " + " ".join(failed))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user