m-ui 的配置校验与回滚是怎么做的
面板能做的最糟的事,是接受一次让代理整个挂掉的改动。m-ui 在"点一下"和"sing-box 跑起来"之间放了四道关,最后一道在前面全错的时候把上一份配置放回去。
第一道:字段检查
API 先检查表单发来的东西:协议是已知的、端口在范围内、JSON 字段能解析、名字没被占、TLS 模式和协议匹配、选了 Reality 就得有密钥、端口跳跃范围规范化。这些便宜、报错也最清楚,所以最先跑。
第二道:端口探测
端口冲突是 sing-box 解析时看不到的那种错误 —— 只有监听器绑定时才会失败。m-ui 把端口和其它线路、面板、订阅、代理面板的端口对照,然后真的绑一次(TCP 和 UDP)来抓机器上别的程序。没改端口的编辑跳过绑定,因为占着它的正是运行中的数据面。把面板挪到新端口时同样的规则反向适用。
第三道:事务里的 sing-box 解析器
有了这一道,前面两道才可以不完美。改动写在 SQLite 事务里;提交之前,m-ui 从数据库渲染出这台服务器的完整配置 —— 每条线路、每个上游、每个用户 —— 交给 sing-box 的选项解析器(core/validate.go)。解析器拒绝,事务就回滚,请求带着 sing-box 自己的错误信息失败。什么都没进运行中的内核,数据库里仍是上一份好状态。
因为渲染的是整份配置,这一道还能抓组合问题:两个上游会生成同一个出站标签、某个用户的凭据不适合新加的协议、某个选项只在和另一个选项同时出现时才非法。
第四道:起不来就回滚
配置可能解析通过却起不来:探测和重启之间端口被别的进程抢了、证书文件没了、内核拒绝某个 socket 选项。改线路会重启数据面,所以疼的就是这里。m-ui 在内存里留着上一份已应用的配置;新的起不来,就把旧的再启动,重新应用按用户的限制和端口跳跃规则,把错误报回面板。你修原因的时候,用户仍然在旧配置上有服务。
仍然抓不住的
- 合法但错的:上游服务器地址打错一个字母、证书和域名对不上。巡检和上游测试按钮就是为这些准备的。
- 机器之外的防火墙。线路跑得好好的,却因为云安全组拦了 UDP 而连不上。
- 客户端兼容性:sing-box 接受的配置,仍可能用了老客户端不认识的选项。
副机
副机从主机收到完整快照,用同样的逻辑应用:解析、按级别应用、重启失败就回滚。应用不了快照的副机把错误报回去,保留上一份配置,下次推送再试 —— 主机从不让副机停在应用了一半的状态。
顺序为什么重要
便宜的检查先跑,报错更明白;昂贵的整份渲染只在便宜的通过后才跑;回滚是为静态检查看不到的情况准备的。每一道都允许不完美,因为下一道在那里。