From 5299e18cd8313fcbf9cc94268a70dd6249dd2cdf Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 12 Sep 2026 08:10:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(config):=20=E5=85=A8=E6=96=B0=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC=E6=92=AD=E7=A7=8D?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E6=94=B9=E4=B8=BA=E6=98=BE=E5=BC=8F=E6=A0=87?= =?UTF-8?q?=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 播种判据曾经是「config 表为空」。而发行包的 postinst 先跑 setup.sh → initconfig,后者会写一行 webui.listen_addr,于是**全新安装**被判定为 "已有配置"并整体跳过播种:没有 core.plugin.dir(装完 0 个插件)、没有 core.memory.* 路径、也没有随包模型对应的多模态 provider——754MB 产物与 24MB 运行库全成死重量。同样的机制此前已在协议 2 迁移演练里被观察到。 也不能改成"每次都补缺键":老安装升级时被注进新默认值,会让它突然去加载 一个 1.8GB 的模型,那是刻意要避免的静默变重。 故改为显式标记 core.internal.seed_version: 有标记 → 已播种,返回 无标记但有 core.daemon.data_dir → 老安装,只补标记、不播种 两者都没有 → 全新安装,播种并打标记 测试:TestSeedDefaultsAfterInitconfigPrepopulate(精确复现 initconfig 的 那一行写入)、TestSeedDefaultsDoesNotInjectIntoLegacyInstall。 --- internal/config/registry.go | 29 ++++++++++++++-- internal/config/registry_test.go | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/internal/config/registry.go b/internal/config/registry.go index e0bd033..59e1413 100644 --- a/internal/config/registry.go +++ b/internal/config/registry.go @@ -482,9 +482,30 @@ func (r *ConfigRegistry) SeedDefaults(dataDir string) { } func (r *ConfigRegistry) seedDBValues(dataDir string) { - var count int - r.db.QueryRow(`SELECT COUNT(*) FROM config`).Scan(&count) - if count > 0 { + // 新鲜度判据不能是「config 表非空」。 + // + // 发行包的 postinst 会先跑 setup.sh → initconfig,而 initconfig 会写一行 + // webui.listen_addr。于是**全新安装**的 DB 看上去"已经有内容",整个默认值 + // 播种被跳过:core.plugin.dir、core.memory.*、多模态 provider 一个都没写。 + // 现场表现是装完 0 个插件、随包的模型与运行库成死重量。 + // + // 也不能改成"每次都补缺键":老安装升级时被注进新默认值,会让它突然 + // 去加载一个 1.8GB 的模型——那是刻意要避免的行为(静默变重)。 + // + // 故用显式标记区分三种情形: + // 有标记 → 已经播过种,直接返回 + // 无标记但有 core.daemon.data_dir → 老安装(本键历来由播种写入), + // 只补标记、不播种 + // 两者都没有 → 全新安装,播种并打标记 + const markerKey = "core.internal.seed_version" + var hasMarker, hasLegacy int + r.db.QueryRow(`SELECT COUNT(*) FROM config WHERE key = ?`, markerKey).Scan(&hasMarker) + if hasMarker > 0 { + return + } + r.db.QueryRow(`SELECT COUNT(*) FROM config WHERE key = 'core.daemon.data_dir'`).Scan(&hasLegacy) + if hasLegacy > 0 { + r.db.Exec(`INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)`, markerKey, "1") return } @@ -604,6 +625,8 @@ WebUI 概览页展示你的立绘,可通过 /mascot.webp 直接访问。如输 set("core.input_processing.audio.fallback_model", "") set("core.input_processing.audio.describe_prompt", "请转写这段音频的内容。") + set(markerKey, "1") + tx.Commit() } diff --git a/internal/config/registry_test.go b/internal/config/registry_test.go index 6169612..29b745e 100644 --- a/internal/config/registry_test.go +++ b/internal/config/registry_test.go @@ -188,6 +188,65 @@ func TestSeedDefaultsToConfig(t *testing.T) { r.Close() } +// 发行包全新安装:postinst 先跑 setup.sh → initconfig,而 initconfig 只写 +// webui.listen_addr。于是 config 表已经非空,旧实现据此判定“已有配置”并整体 +// 跳过播种——装完没有 core.plugin.dir(0 个插件)、也没有随包模型对应的 +// 多模态 provider(754MB 产物 + 24MB 运行库全成死重量)。 +func TestSeedDefaultsAfterInitconfigPrepopulate(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.db") + + r := NewConfigRegistry(path) + // 精确复现 initconfig 的唯一一笔写入 + if _, err := r.db.Exec(`INSERT INTO config (key, value) VALUES ('webui.listen_addr', ':8080')`); err != nil { + t.Fatalf("预置 initconfig 行: %v", err) + } + + r.SeedDefaults(dir) + + for _, k := range []string{"core.daemon.data_dir", "core.plugin.dir", "core.memory.multimodal_space.provider"} { + if r.GetString(k, "") == "" { + t.Fatalf("全新安装(initconfig 已写 webui.listen_addr)后 %s 仍为空:默认值播种被跳过", k) + } + } + if got := r.GetString("core.memory.multimodal_space.provider", ""); got != "chineseclip" { + t.Fatalf("随包默认 provider 应为 chineseclip,实为 %q", got) + } + r.Close() +} + +// 老安装升级:绝不能因为新版本加了默认值就把它注进现有 DB——那会让升级即 +// 静默加载一个 1.8GB 的模型。判据是 core.daemon.data_dir 在场(老安装由播种 +// 写入)而 seed 标记缺失。 +func TestSeedDefaultsDoesNotInjectIntoLegacyInstall(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.db") + + r := NewConfigRegistry(path) + if _, err := r.db.Exec(`INSERT INTO config (key, value) VALUES ('core.daemon.data_dir', ?)`, dir); err != nil { + t.Fatalf("预置老安装行: %v", err) + } + + r.SeedDefaults(dir) + + if got := r.GetString("core.memory.multimodal_space.provider", ""); got != "" { + t.Fatalf("老安装升级被注入新默认值 provider=%q(升级后会静默加载大模型)", got) + } + if got := r.GetString("core.plugin.dir", ""); got != "" { + t.Fatalf("老安装升级被注入新默认值 core.plugin.dir=%q", got) + } + + // 但标记必须补上,否则每次启动都会重走判断 + var n int + if err := r.db.QueryRow(`SELECT COUNT(*) FROM config WHERE key = 'core.internal.seed_version'`).Scan(&n); err != nil { + t.Fatalf("查 seed 标记: %v", err) + } + if n != 1 { + t.Fatalf("老安装应补上 seed 标记,实际 count=%d", n) + } + r.Close() +} + func TestGetHelpers(t *testing.T) { r := NewConfigRegistry("") r.Set("str_key", "hello")