Skip to content

第 7 章:配置增强流水线——把多份意图编译成 Runtime YAML

`enhance()` 是项目的数据心脏。顺序一旦变化,同一组配置就可能得到不同结果。本章逐阶段拆开这条流水线,解释 Sequence、应用默认、内建脚本、TUN/DNS、全局覆盖、Profile 覆盖和权威字段保护。

一、输入与输出

函数签名可以概括为:

rust
async fn enhance() -> Result<(
    Mapping,                       // 最终配置
    HashSet<String>,               // 基础/增强出现过的 key
    HashMap<String, ResultLog>,    // script 日志
)>

输入来自全局 Config 单例和当前 Profile 文件。输出仍是内存 Mapping,写文件和启动核心由 Config/CoreManager 负责。

二、完整顺序

源码顺序如下:

text
1. 收集 IClashTemp / IVerge 开关
2. 读取当前 Profile + 关联增强项
3. Rules / Proxies / Groups sequence
4. 合并 Application Merge Config
5. 执行内建兼容脚本
6. 应用 TUN 派生配置
7. 应用 DNS 页面配置
8. 捕获应用权威字段
9. Global Merge
10. Global Script
11. Profile Merge
12. Profile Script
13. 恢复应用权威字段
14. 修正 allow-lan bind-address
15. 清理无效 proxy group 引用
16. 排序顶层字段

这不是“实现细节”。它就是配置优先级规范。

三、阶段 1:收集稳定快照

get_config_values() 分别取得 Clash 和 Verge 的 latest_arc(),把后续需要的字段复制成 ConfigValues

  • 当前 core 类型;
  • TUN 是否启用且未被本次会话 suppress;
  • 内建增强是否开启;
  • socks/http/redir/tproxy listener 开关;
  • DNS 页面和 external controller 开关。

后续纯函数接收这些值,不再在循环内部反复读取全局配置。这样一次 enhance 使用同一份逻辑快照,也更容易单测。

四、阶段 2:把 Profile 引用解析为 ChainItem

collect_profile_items() 读取当前基础 YAML,并解析七个增强槽位:

  • profile merge/script/rules/proxies/groups;
  • global Merge/Script。

缺失项被替换为空 Mapping、空 SeqMap 或默认 main(config) { return config } 脚本,而不是让整条管线充满 Option 分支。

这是 Null Object 思路:每个阶段始终可执行,只是某些输入不产生变化。

五、阶段 3:Sequence 增强

SeqMap 有三个字段:

rust
struct SeqMap {
    prepend: Sequence,
    append: Sequence,
    delete: Vec<String>,
}

对 rules:按字符串删除,再前置/保留/追加。

对 proxies:除修改 proxies 列表,还会:

  • 从所有 proxy groups 中删除被删节点引用;
  • 将新增节点加到第一个 selector group;
  • 去重新增名称;
  • 保留非标准形态,不擅自丢弃未知数据。

为什么 Sequence 在 Merge/Script 之前?它提供结构化、可预测的基础编辑;后续手动覆盖拥有更高优先级,可以完整替换这些结果。

六、阶段 4:应用控制面配置

merge_default_config()IClashTemp 写入 Profile:

  • tun 做嵌套 merge;
  • 未启用的 socks/http/redir/tproxy 从结果删除;
  • 非 Linux 删除 tproxy;Windows 不接受 redir;
  • external controller 关闭时写空字符串;
  • 其他字段直接覆盖。

函数把所有开关作为参数传入。早期若在循环中读取全局 enable_external_controller,表面纯函数会依赖进程状态,难以测试不同组合。

七、阶段 5:内建兼容脚本

ChainItem::builtin() 为 Mihomo stable/alpha 选择兼容脚本,例如:

  • meta guard;
  • Hysteria ALPN 形态修复。

内建脚本在用户 Merge/Script 之前执行。用户仍可有意识地覆盖兼容修正,但默认情况下应用先把已知历史格式转成当前核心接受的形态。

八、阶段 6:TUN 与 DNS 派生

use_tun(config, enable)

  • 总是把 tun.enable 写成开关值;
  • 开启时若 DNS 是 fake-ip 或未设置 mode,则补齐 DNS enable、fake-ip range;
  • 顶层 IPv6 开启时补 fake-ip-range6
  • 用户明确选择 redir-host 时不改写;
  • 关闭 TUN 时保留用户 DNS 配置,只恢复系统 DNS。

随后 apply_dns_settings 在 DNS 页面拥有控制权时合并独立 DNS 文件。

这两个阶段都属于“应用派生”:用户不是直接编辑最终字段,而是表达 TUN/DNS 意图,应用负责生成一致组合。

九、权威字段:允许扩展,但不允许夺走控制面

Global/Profile Script 能返回任意 Mapping。如果它可以把 external controller 改成公网地址、改变 app 选定端口或偷偷打开 TUN,界面状态与实际核心会分裂。

AuthoritativeFields 在手动覆盖前捕获:

  • controller 与 CORS;
  • secret;
  • 所有 listener port;
  • tun、mode、allow-lan、log-level、ipv6、unified-delay;
  • DNS 页面启用时的 dns.ipv6

手动覆盖后:

  • 捕获时存在的字段恢复原值;
  • 捕获时不存在的控制字段从结果删除,防止脚本重新引入;
  • 非控制字段全部保留。

这建立了清晰所有权:扩展可以改数据面,控制面最终由应用负责。

十、Global 与 Profile 的优先级

执行次序是:

text
Global Merge → Global Script → Profile Merge → Profile Script

因此 Profile 级设置可以覆盖全局设置,Script 可以覆盖同级 Merge。这个顺序符合“越具体越靠后、程序化变换比声明合并更强”的直觉。

每次 Script 前后比较顶层 key,记录 exists_keys;日志按 script uid 保存到 runtime,前端编辑器可查看执行结果。

十一、JavaScript 沙箱不是无限执行器

use_script 使用 Boa JavaScript 引擎,并设置多层限制:

限制防止什么
执行 timeout5 秒长时间占用后台线程
loop iterations10,000,000无限循环
日志条数1,000输出洪泛
日志总大小1 MiB内存放大
输入/结果 JSON10 MiB超大序列化对象
Profile name1,024 字节代码拼接放大

脚本在 spawn_blocking 中执行,外层再用 Tokio timeout。注意:timeout 能让 await 方返回,但不能强制中断已经运行的普通线程;Boa 的 loop iteration limit 是真正限制 CPU 循环的重要第二道防线。

脚本只获得冻结的 console 代理和 config/name 参数,没有 Node.js fs/network 能力。结果必须可 JSON 序列化回 Mapping。

十二、尾部清理

12.1 ensure_lan_bind_address

allow-lan=truebind-address 仍是 loopback(包括 IPv4 简写),自动改为 *。否则用户打开 allow-lan 却仍无法从局域网访问。

12.2 cleanup_proxy_groups

收集 proxies、proxy-groups、proxy-providers 与内建策略,移除 group 中无效引用,避免 Mihomo 校验因增强删除了节点而失败。

12.3 use_sort

将 mode、listener、controller 等可读设置放在文件前部,把 proxies/providers/groups/rules 大列表放到尾部。排序不改变语义,但改善用户打开 runtime YAML 时的可诊断性,并测试幂等。

十三、为什么这条管线可靠

可靠性来自四个结构性约束:

  1. 顺序集中在一个函数,没有跨模块隐式优先级。
  2. 中间阶段尽量纯,可用小 Mapping 测试。
  3. 控制面有明确所有者,扩展不是无限权限。
  4. 输出还要经过 Mihomo 校验,管线正确不等于核心一定接受。

本章源码索引

  • src-tauri/src/enhance/mod.rs::enhance:16 阶段总序
  • src-tauri/src/enhance/chain.rs:ChainItem 解析与内建脚本选择
  • src-tauri/src/enhance/seq.rs::use_seq:序列增强
  • src-tauri/src/enhance/merge.rs::use_merge:递归深合并
  • src-tauri/src/enhance/script.rs::use_script:Boa 执行与资源限制
  • src-tauri/src/enhance/tun.rs::use_tun:TUN/DNS 派生
  • src-tauri/src/enhance/field.rs:lowercase、key 收集与排序
  • src-tauri/src/config/runtime.rs:保存结果、key 与 chain logs

原创文档 CC BY-SA 4.0 · 站点代码 MIT