最后验证:2026-10-02T15:08:38Z

sing-box 控制台(sing-box-panel)使用与设计

~/pkgbuild-source/packages/sing-box-panel/,包名 sing-box-panel,与 sing-box-ebpf 解耦(TUN / eBPF 两种模式都能用)。
后端 Python3 标准库,前端 单页无构建,运行状态页 内嵌 zashboard(MIT,随包分发,不联网加载)。

1. 它解决什么问题

MetaCubeXD / zashboard 这类面板只能读 Clash API,也就是”看”运行状态。你自己的需求里有三件事它们做不到:

需求 Clash 类面板 本面板
运行状态(连接/流量/延迟/日志) ✅ 内嵌 zashboard + 自己的服务卡片与 eBPF 探针
节点 / 订阅 ❌ 抓取 → 解析 → 写入 outbounds → 校验 → 重启
分应用策略 ❌ 写 eBPF 入站的 include_uid / exclude_uid
服务控制 / eBPF 能力预检 ❌ systemd + sing-box tools ebpf status
配置安全应用 ❌ 备份 → check → 原子替换 → 重启 → 健康检查 → 失败自动回滚

2. 安装与启动

# 从私有仓库(CI 构建产物)
sudo pacman -S sing-box-panel

# 或者用本地构建的包
sudo pacman -U sing-box-panel-0.1.0-5-any.pkg.tar.zst

sudo systemctl enable --now sing-box-panel
sudo cat /var/lib/sing-box-panel/token     # 访问令牌(0600;服务首次启动时自动生成,PANEL_TOKEN 可覆盖)

然后浏览器打开 http://127.0.0.1:9096/,把上面的令牌粘进去。令牌存在浏览器 localStorage,点右上角「锁定」可清除。

面板只监听 127.0.0.1;所有 /api/* 请求都要求 X-Panel-Token。静态页面本身不含任何机密。

3. 四个页签

3.1 运行状态

  • 服务卡片:active/enabled、PID、启动时间;启动/重启/重载/停止按钮(systemctl)。
  • 核心卡片:sing-box 版本、入站模式(eBPF / TUN)、配置文件路径与修改时间。
  • 实时流量:连接数、累计上/下行(轮询 Clash API /connections,3s 一次)。
  • eBPF 内核能力预检:点一下就跑 sing-box tools ebpf status --mode all --json,
    逐条列出 PASS / FAIL / UNKNOWN。这是”改配置前先确认内核吃不吃得下”的按钮,
    它不挂载程序、不改动任何状态。要测虚拟机共享路径就在输入框填下游接口(例如 virbr0)。
  • 日志:journalctl -u sing-box 最近 200 行。
  • 集成卡片:Clash API 地址、密钥(一键复制),以及三个入口 ——
    zashboard(内嵌,深链 #/setup?...&secret=… 自动带密钥,点开即配好)、官方 dashboard(127.0.0.1:9091/dashboard/,讲 sing-box 原生 API,排障用)、
    MetaCubeXD(只在 sing-box 配置里启用 clash_api.external_ui 时才出现,见 §9)。

3.2 节点与订阅

  • 订阅:填 URL → 添加并拉取。支持
    • base64 编码的链接列表 / 纯链接列表:ss://(SIP002 与 legacy)、vmess://、vless://、
      trojan://、hysteria2:// / hy2://、tuic://
    • Clash YAML(proxies:);装了 python-yaml 用它,没装则用内置子集解析器
    • 暂不支持的类型(如 snell)会被跳过并在提示里列出,不会污染配置
  • 写入配置:把启用订阅的节点写入 outbounds,并把它们加进托管策略组。
  • 策略组与节点:从 Clash API 实时读;点节点即切换,点「测」做单点延迟测试,「整组测速」批量测。
  • 订阅抓取默认走本机代理口(subscription_proxy,默认 http://127.0.0.1:7892),
    这样机场订阅在国内也能拉到;不需要就把它设成空串。

托管契约(很重要):面板只碰自己上次写进去的东西——
managed.json 记录 outbound_tags(自己加的节点)与 group_members(自己往组里加的成员)。
重新写入时先删掉这些、再加新的,不会动你手工维护的节点和组成员(例如你的 Download 组、
地区组里的手写成员)。默认托管组是 Proxy(selector)与 Auto(urltest),可在面板配置里改。

3.3 分应用策略

  • 模式:关闭 / 黑名单(选中的不走代理)/ 白名单(只有选中的走代理)。
  • 应用列表来自 .desktop 条目 + /proc 扫描,按 UID 聚合(显示进程数与可执行文件)。
  • 也可以手工加 UID。点「应用策略」写回 eBPF 入站的 local.include_uid / exclude_uid,
    然后走安全管线重启核心。
  • Linux 上策略粒度就是 UID(同一 UID 的多个进程一起生效);include_package 是 Android 专用字段,桌面不要用。

3.4 配置与服务

  • 配置编辑器:整份 config.json,可「仅校验」(sing-box check)或「应用并重启」。
  • 应用流程:JSON 解析 → sing-box check → 备份 → 原子替换 → systemctl restart → 健康检查
    (is-active,TUN 模式再等 tun0,可选再经代理口探一个 URL)→ 任何一步失败就写回旧配置并重启。
  • 备份与回滚:每次应用前自动备份到 /etc/sing-box/backups/(保留最近 backup_keep 份,默认 30),
    列表里可一键恢复。恢复本身也走同一条安全管线。

4. 配置文件:/etc/sing-box/panel.json(可选)

面板在缺少该文件时用内置默认值。示例见包里的 /usr/share/sing-box-panel/panel.json.example:

键 默认 说明
listen / port 127.0.0.1 / 9096 监听地址与端口
sing_box_config /etc/sing-box/config.json 被管理的 sing-box 配置
service sing-box systemd 单元名
sing_box_bin /usr/bin/sing-box 用于 check / tools ebpf
clash_api http://127.0.0.1:9090 Clash API 地址(密钥自动从 sing-box 配置的 experimental.clash_api.secret 读取)
state_dir /var/lib/sing-box-panel 令牌、subscriptions.json、managed.json
subscription_proxy http://127.0.0.1:7892 拉订阅走的代理;空串=直连
managed_group / managed_urltest Proxy / Auto 托管组名;设空串则不动组
restart_timeout 20 重启后等健康检查的秒数
health_probe_url 空 设了就用它做端到端探测(经本机代理口 curl);空=只查服务状态
backup_keep 30 保留的备份份数

5. 安全模型

  • 只监听本机 + 令牌鉴权(secrets.compare_digest 常量时间比较);令牌文件 0600。
  • 以 root 运行(systemd 单元里写明原因):写 /etc/sing-box/、systemctl、以及
    sing-box check 遇到 eBPF 入站时会真的创建 BPF map(非 root 必然失败)。
  • 没有任意命令执行接口:只有固定的几类操作(服务控制、配置读写、订阅、策略、探针、日志)。
  • 静态目录做了路径穿越防护(必须是 ui_dir / zashboard_dir 的子路径)。
  • 写配置永远先校验、先备份,失败自动回滚——这条是本面板存在的主要理由。

6. 排障

现象 原因 / 处理
面板起不来,日志 Address already in use 端口被占;改 panel.json 的 port
打开面板一直要求令牌 令牌在 /var/lib/sing-box-panel/token;sudo cat 查看
「应用并重启」提示 check 未通过 … operation not permitted 面板不是以 root 运行(eBPF 配置的 check 需要建 BPF map)。用 systemd 单元启动,别用普通用户前台跑
订阅更新失败 检查 subscription_proxy 是否可达(默认走 127.0.0.1:7892),或把机场域名加进直连规则
zashboard 里没有数据 面板里的入口现在会自动带上密钥(#/setup?…&secret=…)。若你手动输地址,密钥是 experimental.clash_api.secret(你这里是 <clash-api-secret>)
想让面板管理别的组名 改 managed_group / managed_urltest,再点一次「写入配置」
手工改过配置后想同步 「配置与服务」里点「重新载入文件」

7. 已经做过的验证(本机实测)

验证项 结果
后端语法 + dry-run 启动 ✅ py_compile;--dry-run 下不碰 systemd,配置写 /tmp
令牌门 ✅ 无令牌 /api/state → 401;/api/ping 免鉴权仅报告版本
静态服务 ✅ /、/app.js、/style.css 200;/dash/ 与 /dash/assets/*.js 200(zashboard 相对路径直接用)
订阅解析 ✅ ss(SIP002/legacy)、vmess、vless(reality+grpc)、trojan(ws)、hysteria2(obfs)、tuic、Clash YAML 全部解析;snell 正确跳过
发现并修复的解析 bug ✅ trojan 链接缺 security= 时丢失 SNI → 现在保留
订阅写入 + 幂等 ✅ 出站 24→24(不重复堆叠),Proxy 17→19、Auto 11→13,managed.json 只记自己加的东西
分应用写回 ✅ 白名单 → include_uid(去重)、黑名单 → exclude_uid、关闭 → 清空
打包 ✅ makepkg --printsrcinfo 与 .SRCINFO 一致;仓库自带 check-package.sh 通过;容器内 makepkg 构建 + 安装 + 运行 --version 全通
namcap ⚠️ 只剩 2 条误报(退出码 0,不会中断 CI):python3 未声明(Arch 的 python 本身就 Provides: python3);sing-box 可能不需要(面板通过 sing-box check / tools ebpf status 调用它,namcap 看不到)

构建产物(2026-10-02 晚复核并更新版本号):~/.cache/pkgbuild-out/sing-box-panel-0.1.0-5-any.pkg.tar.zst、
~/.cache/pkgbuild-out/sing-box-ebpf-1.14.2.ref1nd-2-x86_64.pkg.tar.zst。

8. 第一次装上后建议这样验证

sudo pacman -U ~/.cache/pkgbuild-out/sing-box-ebpf-*.pkg.tar.zst        # 会用 eBPF 版顶掉官方 sing-box
sudo pacman -U ~/.cache/pkgbuild-out/sing-box-panel-*.pkg.tar.zst
sudo systemctl daemon-reload
sudo systemctl restart sing-box            # 先仍用 TUN 也没关系,面板两种模式都能管
sudo systemctl enable --now sing-box-panel
sudo cat /var/lib/sing-box-panel/token     # 浏览器打开 http://127.0.0.1:9096/ 后粘贴

确认面板三个只读动作正常:运行状态卡片有数据 → eBPF 预检 能跑出 PASS → zashboard ↗ 能打开并显示节点。
然后才是把 config.json 里的 tun 入站换成 ebpf(面板「配置与服务」里直接改,校验+回滚都有)。

追注(2026-10-02 晚):本节写于 TUN 时代,上面这条”把 tun 入站换成 ebpf”的迁移已经完成 ——
当前 /etc/sing-box/config.json 只有 mixed + ebpf 两个入站(local.data_plane: cgroup、
shared.data_plane: packet_rewrite on virbr0,TCX 挂载),没有 tun 入站,tun0 设备也不存在。
首次装机的验证流程本身仍然适用,只是”先仍用 TUN”这句已成为历史。

9. 运行时面板:只留 zashboard

决定(2026-10-02):保留 zashboard,去掉 MetaCubeXD。

zashboard MetaCubeXD
活跃度 ★3.5k,2026-09-30,v3.29.1 ★4.4k,2026-09-30
观感 更现代(PWA、连接 GeoIP/地球视图、多后端 Clash+dae) 成熟但界面偏旧
在你这套里的形态 随面板离线内嵌(2.9 MB,dist-no-fonts),深链自动带密钥,启动不依赖网络 由 sing-box 启动时联网下载到 /var/lib/sing-box/ui,多一个动态件
API Clash(+ dae) Clash

面板里的 MetaCubeXD 入口是数据驱动的:只有配置里存在 clash_api.external_ui 才显示,
所以摘掉配置后按钮自动消失,不用改面板代码。

摘掉 MetaCubeXD(先备份配置、check 通过才重启):

sudo python3 - <<'INNER'
import json, shutil, time
p = '/etc/sing-box/config.json'
shutil.copy2(p, f'/etc/sing-box/config.json.bak.{time.strftime("%Y%m%d-%H%M%S")}')
d = json.load(open(p))
ca = d.get('experimental', {}).get('clash_api', {}) or {}
gone = {k: ca.pop(k) for k in ('external_ui', 'external_ui_download_url', 'external_ui_download_detour') if k in ca}
json.dump(d, open(p, 'w'), ensure_ascii=False, indent=2)
print('已从 clash_api 移除:', gone)
INNER
sudo /usr/bin/sing-box check -C /etc/sing-box && sudo systemctl restart sing-box
sudo rm -rf /var/lib/sing-box/ui        # 删掉已下载的 MetaCubeXD 静态文件

验证:curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9090/ui/ 不再是 200;
面板刷新后 MetaCubeXD 按钮消失;zashboard 仍正常(密钥由面板深链自动带上)。

顺带:摘掉后 http_clients 与 route.default_http_client 就没有使用者了(当初只为下载面板而生),
留着无害,想干净也可以一并删掉。

更正(2026-10-02 晚实测):上面这句不成立 —— 摘掉 external_ui 后这两个仍然有使用者:
当前配置里 route.default_http_client = "proxy-rules",且 services[api].dashboard.http_client = "proxy-rules"
(官方 dashboard 用它取静态资源)。所以不要跟着删,删了 dashboard 会失去出站。
可核对:curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9091/dashboard/ → 200;
而 /var/lib/sing-box/ui 目录确实已不存在(ls /var/lib/sing-box/ui → 没有那个文件或目录)。