每路入站配一组专属策略组,换线不再改配置文件

原本换一条线要改 config.yaml、跑校验、等内核重载,几秒起步;而换线恰恰是这个工具
最高频的动作,把它放在最重的路径上方向就错了。现在每一路配一个 select 组加 AUTO、
FALLBACK 两个,入站的 proxy 永久指向 select,换线只是调一次内核接口。这样做的前提是
选中不会丢:内核把它写进 cache 文件,重启后由 patchSelectGroup 恢复,而
profile.store-selected 默认为真。

链式代理靠给来源订阅复制一份声明、挂上 override.dialer-proxy 实现,因为这个字段写在
策略组上内核只会打条日志然后忽略。副本是 type: file 读原订阅已经落盘的缓存,而不是
自己再拉一次:自己拉的话订阅地址一不可达副本就是空的,那一路入站会一条线都不剩,
表现是端口还在监听却没有任何出口。

顺带把 shared/outbound-chain.ts 里「只有 Selector 接受手动选择」那条断言删了,它是
错的(内核检查的是 SelectAble 接口,URLTest 和 Fallback 也实现了)。界面仍然只让
Selector 可点,但理由换成了另外两种的手动指定会被它们自己撤销。

完整取舍见工作区决策记录 0007。
This commit is contained in:
2026-08-11 03:10:38 +08:00
parent afe53b9790
commit e9e2eb555d
19 changed files with 1807 additions and 567 deletions
+18
View File
@@ -117,6 +117,16 @@ export async function applyConfig(
}
}
// 落盘链路有五步各自可能变慢(校验要起一个内核进程、重载要等内核跑完、备份和写盘是磁盘
// IO)。出问题时不靠猜,日志里直接给出每步耗时。
const clock = { start: Date.now(), mark: Date.now() }
const lap = (stage: string) => {
const now = Date.now()
const ms = now - clock.mark
clock.mark = now
return `${stage} ${String(ms)}ms`
}
const nextYaml = rewriteYaml(current.yaml, nextData)
// 内容没变就不要走后面那一串——不必要的备份和重载只会制造噪音。
@@ -138,9 +148,11 @@ export async function applyConfig(
// 先写到临时文件交给内核校验,别碰真文件。
const tempPath = join(dirname(env.configPath), `.patchbay-check-${String(Date.now())}.yaml`)
let checkTiming = ''
try {
await writeFile(tempPath, nextYaml, 'utf8')
const checked = await validateConfigFile(env, tempPath)
checkTiming = lap('内核校验')
if (!checked.ok) {
return {
ok: false,
@@ -156,6 +168,7 @@ export async function applyConfig(
// 校验过了才动真文件。备份放在写入之前。
const backupPath = await createBackup(env)
const backupTiming = lap('备份')
try {
await writeFile(env.configPath, nextYaml, 'utf8')
} catch (error) {
@@ -168,7 +181,9 @@ export async function applyConfig(
}
}
const writeTiming = lap('写盘')
const reloaded = await reloadConfig(env, env.configPath)
const reloadTiming = lap('重载')
if (!reloaded.ok) {
// 文件已经写进去了但内核没接受。把文件还原,再让内核载回原样,
// 避免留下「文件是新的、内核跑的是旧的」这种最难排查的状态。
@@ -192,6 +207,9 @@ export async function applyConfig(
}
await pruneBackups(env)
console.log(
`[apply] 共 ${String(Date.now() - clock.start)}ms(${checkTiming},${backupTiming},${writeTiming},${reloadTiming})`,
)
return {
ok: true,
snapshot: await readConfig(env),