保存链路:内核在线时跳过 mihomo -t,离线时写盘不再回滚
实测 mihomo -t 占整条保存链路九成以上耗时(4-9.4 秒),而 PUT /configs 自带同一套解析、失败时什么都不应用,配合既有的还原备份等于校验,重载 只要几十到几百毫秒。代价是写盘到还原之间的短暂窗口里磁盘上存在一份 可能有错的配置。 顺带修正离线行为:原来重载连不上会把刚写好的文件还原掉,但内核起不来 时用这个界面修配置正是核心场景,现在离线走 mihomo -t 校验后写盘不重载。
This commit is contained in:
+49
-21
@@ -8,8 +8,14 @@
|
||||
*
|
||||
* 顺序是:
|
||||
* 读当前文件 → 检查是否被外部改过 → 生成新内容 → 自己检查一遍
|
||||
* → 写到临时文件 → mihomo -t 校验 → 备份原文件 → 写入 → 通知内核重载
|
||||
* 最后两步中任何一步出问题,都从备份还原并让内核载回原样。
|
||||
* → 备份原文件 → 写入 → 让内核重载,被拒就从备份还原并载回原样
|
||||
* 重载这一步自带校验:内核先完整解析、解析失败什么都不应用并返回 400
|
||||
* (内核源码 hub/route/configs.go:430-438),还原链路补上「把文件退回去」。
|
||||
*
|
||||
* 内核不在线时没有这层校验,换成:
|
||||
* → 写到临时文件 → mihomo -t 校验 → 备份 → 写入,不重载,内核下次启动时生效
|
||||
* 配置写坏导致内核起不来的时候正是最需要这个界面的时候,这条路必须格外稳,
|
||||
* 也绝不能因为「重载失败」把刚修好的文件又还原回去。
|
||||
*
|
||||
* 「自己检查一遍」不能省。实测 v1.19.29:入站的 `proxy:` 指向一个不存在的
|
||||
* 出站时,`mihomo -t` 会通过、重载会成功,直到有流量走到那个入站才在日志里
|
||||
@@ -24,7 +30,7 @@ import {
|
||||
} from '../vendor/config-editor/index.ts'
|
||||
import type { ConfigDiagnostic, ConfigObject } from '../vendor/config-editor/index.ts'
|
||||
import { restoreInlineCommentAlignment } from '../shared/yaml-cosmetics.ts'
|
||||
import { validateConfigFile, reloadConfig } from './kernel.ts'
|
||||
import { kernelVersion, validateConfigFile, reloadConfig } from './kernel.ts'
|
||||
import type { Env } from './env.ts'
|
||||
|
||||
/** 备份保留的份数。再多没有意义——真要回到很久以前的版本,那是版本控制的活。 */
|
||||
@@ -146,24 +152,32 @@ export async function applyConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// 先写到临时文件交给内核校验,别碰真文件。
|
||||
const tempPath = join(dirname(env.configPath), `.patchbay-check-${String(Date.now())}.yaml`)
|
||||
// 内核在线时不跑 mihomo -t:起一个进程把配置重新解析一遍占整条链路九成以上的耗时
|
||||
// (实测保存 4-10 秒里它占 4-9.4 秒,重载只要几十到几百毫秒),而重载请求自带同一套
|
||||
// 解析、失败时什么都不应用,还原链路补上「把文件退回去」,效果等同校验。代价是写盘到
|
||||
// 还原之间的短暂窗口里磁盘上存在一份可能有错的配置,进程恰好在这时被杀才会留下事故。
|
||||
// 内核不在线时 mihomo -t 是唯一的校验手段。
|
||||
const kernel = await kernelVersion(env)
|
||||
let checkTiming = ''
|
||||
try {
|
||||
await writeFile(tempPath, nextYaml, 'utf8')
|
||||
const checked = await validateConfigFile(env, tempPath)
|
||||
checkTiming = lap('内核校验')
|
||||
if (!checked.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
stage: 'kernel-check',
|
||||
message: checked.message,
|
||||
hint: '内核拒绝了这份配置,真文件一个字节都没动,你可以直接继续改。',
|
||||
restored: false,
|
||||
if (!kernel.online) {
|
||||
// 先写到临时文件交给内核校验,别碰真文件。
|
||||
const tempPath = join(dirname(env.configPath), `.patchbay-check-${String(Date.now())}.yaml`)
|
||||
try {
|
||||
await writeFile(tempPath, nextYaml, 'utf8')
|
||||
const checked = await validateConfigFile(env, tempPath)
|
||||
checkTiming = lap('内核校验')
|
||||
if (!checked.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
stage: 'kernel-check',
|
||||
message: checked.message,
|
||||
hint: '内核拒绝了这份配置,真文件一个字节都没动,你可以直接继续改。',
|
||||
restored: false,
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await rm(tempPath, { force: true })
|
||||
}
|
||||
} finally {
|
||||
await rm(tempPath, { force: true })
|
||||
}
|
||||
|
||||
// 校验过了才动真文件。备份放在写入之前。
|
||||
@@ -182,6 +196,21 @@ export async function applyConfig(
|
||||
}
|
||||
|
||||
const writeTiming = lap('写盘')
|
||||
|
||||
if (!kernel.online) {
|
||||
// mihomo -t 已经把过关,文件留给内核下次启动时读。
|
||||
await pruneBackups(env)
|
||||
console.log(
|
||||
`[apply] 共 ${String(Date.now() - clock.start)}ms(${checkTiming},${backupTiming},${writeTiming},未重载)`,
|
||||
)
|
||||
return {
|
||||
ok: true,
|
||||
snapshot: await readConfig(env),
|
||||
reloaded: false,
|
||||
reloadMessage: '内核未连接,改动已写入文件,等内核启动时生效',
|
||||
}
|
||||
}
|
||||
|
||||
const reloaded = await reloadConfig(env, env.configPath)
|
||||
const reloadTiming = lap('重载')
|
||||
if (!reloaded.ok) {
|
||||
@@ -207,9 +236,8 @@ export async function applyConfig(
|
||||
}
|
||||
|
||||
await pruneBackups(env)
|
||||
console.log(
|
||||
`[apply] 共 ${String(Date.now() - clock.start)}ms(${checkTiming},${backupTiming},${writeTiming},${reloadTiming})`,
|
||||
)
|
||||
const timings = [checkTiming, backupTiming, writeTiming, reloadTiming].filter(Boolean).join(',')
|
||||
console.log(`[apply] 共 ${String(Date.now() - clock.start)}ms(${timings})`)
|
||||
return {
|
||||
ok: true,
|
||||
snapshot: await readConfig(env),
|
||||
|
||||
Reference in New Issue
Block a user