m-uising-box panel

深入 · 2026-09-05

为什么 m-ui 把 sing-box 内嵌进来,而不是管一个外部进程

多数面板写一个 JSON 文件,然后重启一个它管不住的内核。m-ui 把 sing-box 当 Go 库链接进来,在面板进程里跑。后面大部分设计都是从这一个决定推出来的。

"内嵌"在这里指什么

sing-box 用 Go 写,它的 box、入站管理器、出站管理器都是可以直接调用的包。m-ui 的 core/ 从渲染好的配置构造一个 box(NewBox),启动它,并留着入站与出站管理器的句柄。面板要改什么时直接调这些管理器 —— AddInbound、AddOutbound、换一张用户表 —— 而不是写文件再发信号。数据面就是和 HTTP 处理、SQLite 连接同在一个进程里的一组 goroutine。

这带来的四件事

1. 改用户不用重启

sing-box 的入站按凭据维护一张用户表。面板握着入站对象,加人、停用、换凭据时可以原地替换那张表,别的用户的既有连接一个都不碰。管外部进程的面板只能重启它,或者用内核通过 socket 暴露的那点局部重载接口。

2. 精确的流量与连接数据

每用户、每线路、每上游的计数每 10 秒直接从内核自己的跟踪器里读 —— 不解析统计 API,不刮日志。在线源 IP 来自连接跟踪器,跨服务器的设备数限制才因此成为可能。

3. 每次保存都用同一个解析器校验

线路、用户、上游提交之前,m-ui 渲染出整份配置,让 sing-box 自己的选项解析器加载一遍(core/validate.go)。sing-box 启动时会拒绝的,保存时就在数据库事务里被拒绝,错误信息是内核自己的。没有第二套面板侧的校验器可能和真实的那套走样。

4. 一个二进制:安装、备份、更新

没有需要和面板版本对齐的内核版本。安装脚本下载一个文件;更新器替换一个文件;备份就是数据库加证书。不需要 Docker 的安装路径是这个决定的直接结果。

代价

内嵌也意味着面板和数据面共用一个进程:数据面必须重启时,宿主一起重启。m-ui 里有两样东西专门用来把这个代价降下来。

第三个后果是内存:面板的占用包含 sing-box 的。1 GB 的小鸡上这不是问题,而且内核不需要第二台机器或容器。

刻意不做的事

m-ui 不把 sing-box 的原始 JSON 当主界面。线路、上游、用户才是模型;JSON 由它们为每台服务器渲染出来。这让多服务器部署从构造上就一致 —— 同一条记录在每台副机上生成同一个入站 —— 代价是不支持 sing-box 的每一个选项。每条线路有一个"高级"字段,接受表单没覆盖到的入站参数。

相关:热更新是怎么做的 · 架构总览 · GitHub 上的 core/