Chimera 运行时配置生成机制说明
1. 关键结论
核心(例如 chimera_client / mihomo)在启动和热重载时使用的配置不是直接从 profiles.yaml 读取的。核心真正使用的是一个运行时文件:
clash-config.yaml(运行时配置,位于app_config_dir下)
这个文件首先由后端在内存中组装,然后写入磁盘,最后通过启动参数或Clash API 热重载传递给核心。
2. 配置输入(原材料)
运行时配置由四类输入构建而成:
-
应用设置:
chimera-config.yaml- 结构体:
IVerge - 加载入口:
backend/tauri/src/config/chimera/mod.rs→IVerge::new() - 用途:控制字段过滤、端口策略、TUN / 系统代理行为等。
- 结构体:
-
Clash Guard 覆盖模板:
clash-guard-overrides.yaml- 结构体:
IClashTemp - 加载入口:
backend/tauri/src/config/clash/mod.rs→IClashTemp::new() - 用途:强制覆盖关键字段(例如
mode、mixed-port、external-controller、secret等)。
- 结构体:
-
配置元数据:
profiles.yaml- 结构体:
Profiles - 加载入口:
backend/tauri/src/config/profile/profiles.rs→Profiles::new() - 用途:记录当前激活的配置(
current)和配置列表(items)
- 结构体:
-
具体配置内容文件:
app_config_dir/profiles/*.yaml- 加载入口:
Profiles::current_mappings() - 用途:提供代理、规则、DNS、TUN 等实际配置内容。
- 加载入口:
3. 启动初始化阶段
3.1 创建基础文件(如果缺失)
backend/tauri/src/utils/init/mod.rs → init_config() 会确保以下文件存在:
clash-guard-overrides.yaml(默认通过IClashTemp::template()生成)chimera-config.yaml(默认通过IVerge::template()生成)profiles.yaml(一个空 / 默认配置)
3.2 加载全局配置对象
backend/tauri/src/config/core.rs → Config::global() 会初始化:
Profiles::new()IVerge::new()IClashTemp::new()IRuntime::new()
IRuntime 是内存中的运行时配置容器;其 config 字段类型为 Option<Mapping>。
4. 运行时配置主组装流程
主入口:Config::generate()(backend/tauri/src/config/core.rs)
flowchart TD A["Start Config::generate"] --> B["Call enhance::enhance"] B --> C["Read YAML(s) of current profile"] C --> D["merge_profiles: merge configs"] D --> E["(Optional) whitelist-based field filtering"] E --> F["Override key fields (HANDLE_FIELDS)"] F --> G["Write into in-memory runtime config"] G --> H["Write out clash config YAML"] H --> I["Load at startup or hot-reload via PUT /configs"]
4.1 enhance::enhance() 做了什么
位置:backend/tauri/src/enhance/mod.rs
核心步骤:
-
加载 Clash Guard 配置
let clash_config = Config::clash().latest().0.clone()
-
读取当前功能开关 / 设置
- 例如从
IVerge读取enable_clash_fields
- 例如从
-
加载当前激活配置的内容
- 通过
Profiles::current_mappings() - 该方法会遍历
current,逐个读取profiles/<file>.yaml,并将其转换为Mapping
- 通过
-
(预留)执行配置链脚本
- 调用
process_chain(...) - 当前实现只是占位(no-op),会直接返回原始配置
- 调用
-
合并多个配置文件
-
调用
merge_profiles(...) -
当前策略:
- 第一份配置:完整
extend - 后续配置:仅将
proxies追加到现有的proxies
- 第一份配置:完整
-
-
白名单字段过滤(可选,由开关控制)
use_whitelist_fields_filter(...)- 当
enable_clash_fields = true时,仅保留valid + default fields中的键
-
强制覆盖 Guard 字段
- 将
IClashTemp中HANDLE_FIELDS列出的字段写回最终配置 - 确保关键控制字段由客户端集中管理
- 将
4.2 HANDLE_FIELDS 覆盖范围
定义于 backend/tauri/src/enhance/field.rs:
modeportsocks-portmixed-portallow-lanlog-level- ipv6
secretexternal-controller
这意味着,即使这些字段存在于配置文件中,其最终值也会被 clash-guard-overrides.yaml 中对应的值覆盖。
5. 写入磁盘与文件位置
入口:Config::generate_file(ConfigType::Run)(backend/tauri/src/config/core.rs)
Run模式输出:app_config_dir()/clash-config.yamlCheck模式输出:temp_dir()/clash-config-check.yaml
如果生成失败,Config::init_config() 会提供回退方案:直接将 IClashTemp 写为运行时配置。
6. 核心如何获取这份配置
6.1 启动时加载
CoreManager::run_core() → Instance::try_new()(backend/tauri/src/core/clash/core.rs):
- 调用
Config::generate_file(ConfigType::Run)获取路径 - 通过
CoreInstanceBuilder.config_path(config_path)将该路径传给核心进程
换句话说:核心在启动时会直接读取 clash-config.yaml。
6.2 运行时热重载
CoreManager::update_config() 的流程:
Config::generate().await?重新组装内存中的配置check_config().await?使用检查文件校验语法 / 可用性generate_file(Run)重写clash-config.yaml- 调用
PUT /configs,并附带{ "path": "<absolute path>" }请求体,以指示核心重载
相关代码:
backend/tauri/src/core/clash/core.rs→update_config()backend/tauri/src/core/clash/api.rs→put_configs(...)
7. 会触发重建的用户操作
7.1 切换 / 修改配置选择
前端 commands.patchProfilesConfig → 后端 patch_profiles_config(...):
- 应用草稿:
Config::profiles().draft().apply(...) - 触发
CoreManager::update_config() - 成功时:
Config::profiles().apply()+save_file() - 失败时:
discard()(回滚)
7.2 修改设置(某些字段)
前端 commands.patchVergeConfig → 后端 feat::patch_verge(...):
-
先写入一份
IVerge草稿 -
某些字段(例如
enable_tun_mode)可能触发:Config::generate()+run_core()(重启场景)- 或
update_core_config()(热更新场景)
7.3 导入第一份配置
当 import_profile(...) 成功后,如果当前还没有激活配置,它会自动构造 ProfilesBuilder.current = [new_uid],并复用 patch_profiles_config(...) 触发更新。
8. 关键细节与常见误解
-
profiles.yaml不是核心最终使用的配置- 它只存储配置元数据和
current指针
- 它只存储配置元数据和
-
配置内容文件不会原样传给核心
- 它们只有在经过合并、过滤和 guard 覆盖之后,才会变成运行时配置
-
external-controller的端口可能会在启动前被修改prepare_external_controller_port()会按策略检查端口可用性,并在必要时切换端口
-
verge_mixed_port主要用于系统代理逻辑- 它不会被直接写入运行时 YAML 的
mixed-port - 系统代理会优先使用
verge_mixed_port,否则回退到Config::clash().get_mixed_port()
- 它不会被直接写入运行时 YAML 的
-
get_runtime_yaml()返回的是内存中的IRuntime.config- 它通常与最近写入的
clash-config.yaml保持一致 - 但从根本上说,它来自内存,而不是每次都重新读取磁盘
- 它通常与最近写入的
9. 当前实现限制(以当前代码库为准)
-
链式脚本执行目前仍是占位实现
process_chain(...)目前尚未真正改写配置
-
全局链处理代码仍处于注释状态
- 目前仅存在带作用域的链式框架
-
patch_clash_configIPC 仍然是todo!()- 如果前端使用该 IPC 路径,将会失败
-
直接编辑配置文件不会自动触发热重载
save_profile_file(...)只会写入文件;它不会调用update_config()
10. 故障排查清单(实用顺序)
如果你怀疑“核心使用了错误的配置”,请按以下顺序检查:
-
确认当前激活的配置是否正确
- 检查
profiles.yaml→current
- 检查
-
确认配置源内容符合预期
- 检查
app_config_dir/profiles/*.yaml
- 检查
-
检查 guard 覆盖项
- 确认
clash-guard-overrides.yaml中的HANDLE_FIELDS是否覆盖了你预期的值
- 确认
-
检查最终运行时配置
- 检查
clash-config.yaml - 或调用
get_runtime_yaml()查看内存中的版本
- 检查
-
确认热重载是否真的发生
- 确认
patch_profiles_config/patch_verge_config/restart_sidecar是否已执行 - 检查日志,确认
PUT /configs是否成功
- 确认
-
如果你遇到端口相关问题
- 检查
external-controller是否被端口策略改写
- 检查