diff --git a/README.md b/README.md index b9f57a21..5c4fbea5 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,435 @@ -> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents +> Sponsored by CodeX - -Warp sponsorship - +# sing-box Xboard Fork ---- +这是一个基于上游 [SagerNet/sing-box](https://github.com/SagerNet/sing-box) 的定制分支,主要面向 Xboard / UniProxy 面板联动部署场景。 -# sing-box +它保留了上游 `sing-box` 内核能力,同时补充了: -The universal proxy platform. +- Xboard 动态入站服务 +- 多节点托管 +- 面板协议自动识别 +- VLESS / REALITY 面板字段映射 +- Shadowsocks 2022 用户同步与密钥处理 +- AnyTLS / Trojan / Hysteria / TUIC 的本地 TLS / ACME 支持 +- 安装脚本与分离式配置模板 +- PROXY protocol 客户端真实 IP 传递 -## Repository Notice +如果你要查看官方功能基线、通用配置文档、原始内核行为,请优先参考上游: -This repository is a customized fork based on the upstream [SagerNet/sing-box](https://github.com/SagerNet/sing-box) project. +- 上游仓库:https://github.com/SagerNet/sing-box +- 上游文档:https://sing-box.sagernet.org -- Upstream project: `sing-box` -- Upstream documentation: https://sing-box.sagernet.org -- This repository may contain local modifications for Xboard integration, deployment scripts, and protocol handling behavior +## 适用场景 -If you are looking for the official project, feature baseline, or upstream release notes, please refer to the upstream `sing-box` repository first. +这个仓库更适合以下用途: -[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions) +- 从 Xboard / UniProxy 面板拉取节点配置 +- 从面板拉取用户并动态更新 +- 单机运行多个节点 +- 给面板型机场节点准备安装脚本 +- 对接面板下发的 `protocol`、`cert_config`、`accept_proxy_protocol`、REALITY 字段等 -## Documentation +## 主要特性 -https://sing-box.sagernet.org +- `services.xboard` 动态服务 +- 支持单节点和多节点 +- 协议优先从面板返回的 `protocol` 识别 +- 支持面板回包控制 `accept_proxy_protocol` +- 支持 VLESS REALITY 的 `server_name`、`public_key`、`private_key`、`short_id` +- 支持 ACME: + - `auto_tls` + - `cert_mode = http` + - `cert_mode = dns` +- 当前已支持的 ACME DNS provider: + - `cloudflare` + - `alidns` + - `tencentcloud` + - `dnspod` + - `acmedns` +- 安装脚本默认生成: + - `/etc/sing-box/config.d/10-base.json` + - `/etc/sing-box/config.d/20-outbounds.json` +- 安装后的服务名为: + - `singbox.service` + +## 仓库内关键文件 + +- [install.sh](./install.sh) + Linux 安装脚本 +- [option/xboard.go](./option/xboard.go) + `services.xboard` 配置结构 +- [service/xboard/service.go](./service/xboard/service.go) + Xboard 动态服务实现 +- [configs](./configs) + 配置示例目录 + +## 快速开始 + +### 1. 编译并安装 + +在 Linux 服务器上进入仓库目录: + +```bash +chmod +x install.sh +./install.sh +``` + +脚本会做这些事情: + +1. 检查 Go 环境 +2. 编译当前仓库代码 +3. 生成分离配置 +4. 创建 `singbox.service` +5. 启动服务 + +### 2. 安装过程会询问的内容 + +- `Panel URL` +- `Panel Token` +- 一个或多个 `Node ID` +- DNS 模式: + - `udp` + - `local` +- 当前节点前面是否有发送 PROXY protocol 的四层代理 + +### 3. 多节点输入规则 + +- 安装脚本会持续要求输入 `Node ID` +- 输入 `NO` 才结束 +- 至少要有一个节点 + +## 运行与管理 + +查看状态: + +```bash +systemctl status singbox +``` + +查看日志: + +```bash +journalctl -u singbox -f +``` + +重启服务: + +```bash +systemctl restart singbox +``` + +手动运行: + +```bash +sing-box -D /var/lib/sing-box -C /etc/sing-box/config.d run +``` + +## 推荐配置结构 + +建议把配置拆成两部分。 + +### `10-base.json` + +放这些内容: + +- 日志 +- DNS +- `services` +- 基础路由规则 + +### `20-outbounds.json` + +放这些内容: + +- 全部出站 +- 出站标签 +- 你自己的分流依赖 + +这样更方便: + +- 面板动态服务和你的自定义出站分离 +- 安装脚本生成的基础配置不容易被误改 +- 调整出站时不会影响 Xboard 服务主体 + +示例已放在: + +- [configs/10-base.single-node.json](./configs/10-base.single-node.json) +- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json) +- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json) + +## `services.xboard` 配置说明 + +配置结构定义见 [option/xboard.go](./option/xboard.go)。 + +### 最小单节点示例 + +```json +{ + "type": "xboard", + "panel_url": "https://panel.example.com", + "key": "replace-with-node-token", + "sync_interval": "1m", + "report_interval": "1m", + "node_id": 286 +} +``` + +### 多节点示例 + +```json +{ + "type": "xboard", + "panel_url": "https://panel.example.com", + "key": "replace-with-node-token", + "sync_interval": "1m", + "report_interval": "1m", + "nodes": [ + { + "node_id": 286 + }, + { + "node_id": 774 + } + ] +} +``` + +### 当前推荐做法 + +- 只传 `panel_url` +- 只传 `key` +- 只传 `node_id` 或 `nodes` + +下面这些字段仍然保留兼容,但常规部署一般不需要: + +- `config_panel_url` +- `user_panel_url` +- `config_node_id` +- `user_node_id` +- `node_type` + +说明: + +- 当前逻辑会优先从面板回包中的 `protocol` 自动识别协议 +- `node_type` 更适合做历史兼容,不建议再依赖它做主配置 + +## 面板配置回包约定 + +### 1. 协议识别 + +推荐面板显式返回: + +```json +{ + "protocol": "vless" +} +``` + +当前服务会按如下顺序识别协议: + +1. `protocol` +2. `node_type` +3. 如果是 Shadowsocks 且存在 `cipher`,则回退识别为 `shadowsocks` + +### 2. 监听地址与端口 + +推荐面板返回: + +```json +{ + "listen_ip": "0.0.0.0", + "server_port": 443 +} +``` + +### 3. PROXY protocol + +如果前面有四层代理,并且它会发送 PROXY protocol 头,需要面板返回: + +```json +{ + "accept_proxy_protocol": true +} +``` + +如果用户是直连节点,不要开启这个字段,否则连接会失败。 + +### 4. VLESS REALITY + +当前支持从面板获取这些字段: + +- `tls_settings.server_name` +- `tls_settings.public_key` +- `tls_settings.private_key` +- `tls_settings.short_id` +- 顶层 `server_name` +- 顶层 `public_key` +- 顶层 `private_key` +- 顶层 `short_id` + +示例见: + +- [configs/panel-response.vless-reality.json](./configs/panel-response.vless-reality.json) + +### 5. Shadowsocks 2022 + +推荐面板返回: + +- `protocol: "shadowsocks"` +- `cipher` +- `server_key` + +示例见: + +- [configs/panel-response.shadowsocks2022.json](./configs/panel-response.shadowsocks2022.json) + +### 6. AnyTLS / Trojan / Hysteria / TUIC 的证书要求 + +这些协议通常要求本地具备可用 TLS 证书。当前支持: + +- 文件证书 +- 证书内容直传 +- ACME 自动签发 + +如果面板没有下发可用证书,也没有有效 ACME 配置,常见报错是: + +```text +Xboard setup error: missing certificate +``` + +示例见: + +- [configs/panel-response.anytls-acme-dns.json](./configs/panel-response.anytls-acme-dns.json) + +## ACME 说明 + +当前支持: + +- `auto_tls: true` +- `cert_mode: "http"` +- `cert_mode: "dns"` + +### DNS-01 provider + +已支持: + +- `cloudflare` +- `alidns` +- `tencentcloud` +- `dnspod` +- `acmedns` + +### DNSPod 说明 + +仓库已经内置 DNSPod provider 适配,不再依赖旧版 `github.com/libdns/dnspod` 的接口兼容。 + +你可以从面板下发: + +```json +{ + "cert_config": { + "cert_mode": "dns", + "dns_provider": "dnspod", + "dns_env": { + "DNSPOD_TOKEN": "id,token" + } + } +} +``` + +也可以下发腾讯云凭据: + +```json +{ + "cert_config": { + "cert_mode": "dns", + "dns_provider": "tencentcloud", + "dns_env": { + "TENCENTCLOUD_SECRET_ID": "xxx", + "TENCENTCLOUD_SECRET_KEY": "xxx" + } + } +} +``` + +## 配置示例目录 + +[configs](./configs) 目录中已经提供了这些示例: + +- [configs/10-base.single-node.json](./configs/10-base.single-node.json) + 单节点基础配置 +- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json) + 多节点基础配置 +- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json) + 出站配置模板 +- [configs/panel-response.vless-reality.json](./configs/panel-response.vless-reality.json) + VLESS REALITY 面板回包 +- [configs/panel-response.shadowsocks2022.json](./configs/panel-response.shadowsocks2022.json) + Shadowsocks 2022 面板回包 +- [configs/panel-response.anytls-acme-dns.json](./configs/panel-response.anytls-acme-dns.json) + AnyTLS + ACME DNS 验证面板回包 + +## 常见问题 + +### `unsupported protocol: empty` + +原因通常是: + +- 面板没有返回 `protocol` +- 兼容字段 `node_type` 也为空 + +建议: + +- 面板显式返回顶层 `protocol` + +### `Xboard setup error: missing certificate` + +原因通常是: + +- AnyTLS / Trojan / Hysteria / TUIC 需要证书 +- 面板没有下发证书文件、证书内容或 ACME 参数 + +### `TLS handshake: REALITY: processed invalid connection` + +多数情况下表示客户端参数和当前 REALITY 节点配置不匹配,例如: + +- `server_name` 错误 +- `public_key` 错误 +- `short_id` 错误 +- 客户端实际上不是按 REALITY 模式连入 + +### `Server does not exist` + +通常是: + +- 面板里不存在该 `node_id` +- `token` 不匹配 +- 拉取配置或拉取用户的节点 ID 写错 + +### 真实 IP 没有正确获取 + +请确认: + +1. 前置代理确实发送了 PROXY protocol +2. 面板确实下发了 `accept_proxy_protocol: true` + +否则服务只能看到上游代理 IP。 + +## 开发验证 + +近期已验证通过的命令: + +```bash +go test ./common/dnspod ./common/tls ./service/acme ./service/xboard +go build -trimpath -tags 'with_quic,with_utls,with_clash_api,with_gvisor,with_acme' ./cmd/sing-box +``` + +如果你改动了 Xboard、协议映射、ACME 或证书相关逻辑,建议至少执行一次以上命令。 ## License -``` +```text Copyright (C) 2022 by nekohasekai This program is free software: you can redistribute it and/or modify @@ -38,7 +439,7 @@ the Free Software Foundation, either version 3 of the License, or This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License diff --git a/configs/10-base.multi-node.json b/configs/10-base.multi-node.json new file mode 100644 index 00000000..185fe0d3 --- /dev/null +++ b/configs/10-base.multi-node.json @@ -0,0 +1,50 @@ +{ + "log": { + "level": "info", + "timestamp": true + }, + "experimental": { + "cache_file": { + "enabled": true, + "path": "/var/lib/sing-box/cache.db" + } + }, + "dns": { + "servers": [ + { + "tag": "dns-local", + "type": "local" + } + ] + }, + "services": [ + { + "type": "xboard", + "panel_url": "https://panel.example.com", + "key": "replace-with-node-token", + "sync_interval": "1m", + "report_interval": "1m", + "nodes": [ + { + "node_id": 286 + }, + { + "node_id": 774 + }, + { + "node_id": 815 + } + ] + } + ], + "inbounds": [], + "route": { + "rules": [ + { + "protocol": "dns", + "action": "hijack-dns" + } + ], + "auto_detect_interface": true + } +} diff --git a/configs/10-base.single-node.json b/configs/10-base.single-node.json new file mode 100644 index 00000000..749920cf --- /dev/null +++ b/configs/10-base.single-node.json @@ -0,0 +1,42 @@ +{ + "log": { + "level": "info", + "timestamp": true + }, + "experimental": { + "cache_file": { + "enabled": true, + "path": "/var/lib/sing-box/cache.db" + } + }, + "dns": { + "servers": [ + { + "tag": "dns-upstream", + "type": "udp", + "server": "1.1.1.1", + "server_port": 53 + } + ] + }, + "services": [ + { + "type": "xboard", + "panel_url": "https://panel.example.com", + "key": "replace-with-node-token", + "sync_interval": "1m", + "report_interval": "1m", + "node_id": 286 + } + ], + "inbounds": [], + "route": { + "rules": [ + { + "protocol": "dns", + "action": "hijack-dns" + } + ], + "auto_detect_interface": true + } +} diff --git a/configs/20-outbounds.example.json b/configs/20-outbounds.example.json new file mode 100644 index 00000000..5573444d --- /dev/null +++ b/configs/20-outbounds.example.json @@ -0,0 +1,12 @@ +{ + "outbounds": [ + { + "type": "direct", + "tag": "direct" + }, + { + "type": "block", + "tag": "block" + } + ] +} diff --git a/configs/panel-response.anytls-acme-dns.json b/configs/panel-response.anytls-acme-dns.json new file mode 100644 index 00000000..347f717d --- /dev/null +++ b/configs/panel-response.anytls-acme-dns.json @@ -0,0 +1,33 @@ +{ + "protocol": "anytls", + "listen_ip": "0.0.0.0", + "server_port": 45365, + "network": null, + "networkSettings": null, + "server_name": "code.example.com", + "accept_proxy_protocol": false, + "padding_scheme": [ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" + ], + "cert_config": { + "cert_mode": "dns", + "domain": "code.example.com", + "dns_provider": "tencentcloud", + "dns_env": { + "TENCENTCLOUD_SECRET_ID": "replace-with-secret-id", + "TENCENTCLOUD_SECRET_KEY": "replace-with-secret-key" + } + }, + "base_config": { + "push_interval": 60, + "pull_interval": 60 + } +} diff --git a/configs/panel-response.shadowsocks2022.json b/configs/panel-response.shadowsocks2022.json new file mode 100644 index 00000000..509e8e2d --- /dev/null +++ b/configs/panel-response.shadowsocks2022.json @@ -0,0 +1,16 @@ +{ + "protocol": "shadowsocks", + "listen_ip": "0.0.0.0", + "server_port": 30009, + "network": null, + "networkSettings": null, + "cipher": "2022-blake3-aes-256-gcm", + "plugin": null, + "plugin_opts": null, + "server_key": "NjQzMWVlNjVmYTkwODk0OTMyOTg3MzZmYzczMmFlMTI=", + "accept_proxy_protocol": false, + "base_config": { + "push_interval": 60, + "pull_interval": 60 + } +} diff --git a/configs/panel-response.vless-reality.json b/configs/panel-response.vless-reality.json new file mode 100644 index 00000000..4eb129f6 --- /dev/null +++ b/configs/panel-response.vless-reality.json @@ -0,0 +1,21 @@ +{ + "protocol": "vless", + "listen_ip": "0.0.0.0", + "server_port": 18443, + "network": "tcp", + "tls": 2, + "flow": "xtls-rprx-vision", + "accept_proxy_protocol": false, + "tls_settings": { + "server_name": "git.example.com", + "server_port": "443", + "public_key": "replace-with-client-visible-public-key", + "private_key": "replace-with-server-private-key", + "short_id": "0123456789abcdef", + "allow_insecure": false + }, + "base_config": { + "push_interval": 60, + "pull_interval": 60 + } +} diff --git a/install.sh b/install.sh index 18195257..53f814a5 100644 --- a/install.sh +++ b/install.sh @@ -95,10 +95,6 @@ build_sing_box() { echo -e "${RED}Failed to download Go modules.${NC}" exit 1 fi - if ! go get github.com/libdns/dnspod@v0.0.3 github.com/libdns/tencentcloud@v1.4.3; then - echo -e "${RED}Failed to download ACME DNS provider modules.${NC}" - exit 1 - fi if ! go mod tidy; then echo -e "${RED}Failed to refresh Go module metadata.${NC}" exit 1