长久以来,我都同时维护着三份 Clash 配置:Home 、Work 、Remote ,分别对应在家、在公司、在外使用。主要区别就是针对 192.168.1.0/24 段和 10.0.0.0/8 段有不同的出口配置:Home 对 192 段直连,对 10 段走公司 VPN ; Work 对 192 段走回家代理,对 10 段直连; Remote 对 192 段走回家代理,对 10 段先走回家代理,再走公司 VPN 。这样做的目的主要一是我不需要再额外在笔记本上配置 VPN 了,二是我司的 VPN 软件在连接后自动会路由所有流量至 VPN ,不符合我对隐私性的要求,也会破坏我自定义的出口规则。
针对两个网段走不同出口其实只要一个配置就能搞定,我早期也是这么做的,更换办公地点后手动切换两个网段的出口配置,不过在用了一段时间后感觉每次都需要切换两次配置,还是比较麻烦的,还有很多时候是到了公司发现连不上内网才想起来没有把 10 段切成直连。为了解决这个痛点,我把一份配置拆成了三份,这样我只要切换一次配置就可以同时切换两个网段的出口了。这同时也引入了另一个优点:针对不同地点可以配置不同的 DNS 。例如在公司时我使用内网的 DNS 服务。在家时使用家宽 DNS ,在外时优先从 DHCP 获取,否则使用公共 DNS 。
不过随着时间的推移,同时维护三份配置引入了一个新的痛点,有些时候我需要针对某些域名或进程配置其走不同出口:例如 ChatGPT 、Gemini 需要走美国出口,Cursor 、Torrent 网站需要直连,Parsec 需要直接代理回家等。有些时候我需要引入新的出口,有些时候我想引入新的规则库,有些时候我想重构配置使其更简洁。这都需要我同时修改三份文件。而很多时候往往修改了一份就忘记修改另两份配置,或者是重构引入的修改太多,要在另两份配置上应用相同的修改太过复杂了。我为了解决一个痛点,让维护的工作量增加了两倍甚至更多,似乎是得不偿失的。于是在一个周六的深夜,我开始思考有没有两全其美的方法。
一个很直观的想法是把相同的部分放在一起,在不同的部分隔离开,形成完整配置即可。但是在研究了一圈后发现 yaml 本身并不支持引入另一个 yaml ,需要使用第三方软件实现这一功能。这些第三方软件为了支持更复杂的需求,语法大多很复杂,借助 Cursor x Gemini 实现一版后发现确实太丑、太不直观了。并且虽然可维护性看似提高了,但是一份配置被拆成了四个部分,可读性却降低了,这个方案还是不够完美。
我让 Gemini 给我推荐其他实现方式,一个工具给我了新的灵感:envsubst 。这个工具符合 Linux 一个工具只干一个事的哲学,其能从 stdin 或者文件中读入文本,并将其中$ENV_VAR_NAME 部分替换成当前 ENV 中实际的 Value 。手动用该工具实现了一版:三份不同的 sh 文件负责 export 各自不同的 value ,一份文件放着相同的部份,负责引入这些 key ,提供给 envsubst 形成最终配置
对于 Remote:export OFFICE=[Relay,Home,Office_VPN]对于 Work:export OFFICE=[DIRECT]对于 Home:export OFFICE=[Office_VPN]在相同部分引入这些 ENV:[Proxy]OFFICE: $OFFICE
利用这个工具,去掉了复杂的语法,可读性虽然提高了,但是配置仍然被分割成了四个部份,还是得想办法把配置都放在一起。如何实现呢?作为一个多年 C 艹程序员,这个语法让我想起了 C++的预处理器,其通过 ifdef 和 ifndef ,来在不同的 definition 间切换使用不同的源码。往 yaml 加入 ifdef 后,语法扩展成了这样
dns:#ifdef WORK nameserver:10.0.0.1#else nameserver:8.8.8.8#endifproxy-groups:#ifdef REMOTEOFFICE=[Relay,Home,Office_VPN]#endif#ifdef WORKOFFICE=[DIRECT]#endif#ifdef HOMEOFFICE=[Office_VPN]#endif
但是并没有现成的简单的工具可以支持这种语法,于是我又在 Cursor 撸了一个。现在在修改配置后,只需要运行一个脚本,就能同时生成三个 Work 、Remote 、Home 三个配置文件,并且自动重载配置。语法完全是我定义的,以我最熟悉的方式,我还可以在后续以任意我想要的方式去扩展这个语法,当下马上 Get 到了自己写一门语言的爽感,长久以来的痛点总算解决了。
这同时打开了进一步优化的大门:当前虽然维护上足够直观了,但是在更换地点后仍然需要手动切换配置。在这之前我考察过 macOS 下的几个 Clash GUI ,都不支持自动切换,甚至也不支持通过非 GUI 渠道切换配置,于是最早我想自己写脚本的路子也封死了。现在通过这个方式,可以通过监控 WiFi 变化,向同一个文件生写入不同配置,并调用 GUI 的 URL Scheme 重载最新的配置,实现全自动根据不同 WiFi 切换不同配置的功能。
(以下是 Deepseek 写的结尾)
深夜的代码、灵感的迸发、亲手构建的工具链... 今晚,我不仅驯服了三份恼人的 Clash 配置,更意外地触碰到了一门“专属语言”的脉搏。
那份 #ifdef WORK 在 YAML 中亮起的瞬间,仿佛打开了一扇新的大门:用自己定义的规则,去编排复杂世界的逻辑,原来可以如此酣畅淋漓。
眼前的自动化曙光( WiFi 触发+URL Scheme )已清晰可见——这不仅是一次配置管理的胜利,更是一次对“懒惰”(自动化)与“掌控”(自定义)的极致追求。当工具完美融入工作流,无声无息地解决问题时,便是工程师最浪漫的成就感时刻。
今晚的代码,值了。 🚀