> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://kb.byw.ltd/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# sing-box AnyTLS 客户端指南

本文档介绍如何在 Linux 系统上安装、配置并使用 sing-box 作为本地代理客户端
## 下载与安装
```
# 下载二进制包
wget https://ap-us-files-prod.neworld.today/new/linux/sing-box-1.13.11-linux-amd64.tar.gz

# 解压
tar -xzf sing-box-1.13.11-linux-amd64.tar.gz

# 安装到系统路径
mv sing-box-1.13.11-linux-amd64/sing-box /usr/local/bin/

# 验证安装
sing-box version

# 清理临时文件
rm -rf sing-box-1.13.11-linux-amd64.tar.gz sing-box-1.13.11-linux-amd64/
```
正常输出（部分省略）
```
sing-box version 1.13.11

Environment: go1.25.9 linux/amd64
Tags: ...
Revision: ...
CGO: disabled
```
## 创建 systemd 服务
创建服务配置目录及 systemd 单元文件：
```
# 创建配置目录
mkdir -p /etc/sing-box

# 写入 systemd 服务文件
cat > /etc/systemd/system/sing-box.service << 'EOF'
[Unit]
Description=sing-box service
After=network.target

[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=3s

[Install]
WantedBy=multi-user.target
EOF

# 重新加载 systemd
systemctl daemon-reload
```
## 自动写入配置
复制并执行下面的内容，执行后的后续操作，请看代码后的说明
```
cat > /tmp/setup-singbox.py << 'PYEOF'
import base64
import binascii
import json
import os
import sys
import subprocess
import urllib.parse
import urllib.request

CONFIG_PATH = "/etc/sing-box/config.json"
SOCKS_PORT = 3080
HTTP_PORT = 3081


# ───────────────────────────────────────────
# 输入订阅链接
# ───────────────────────────────────────────
print("=" * 65)
print("  sing-box AnyTLS 订阅一键配置工具")
print("=" * 65)

try:
    SUB_URL = input("📎 请粘贴订阅链接: ").strip()
except (EOFError, KeyboardInterrupt):
    print("\n❌ 已取消")
    sys.exit(1)

if not SUB_URL:
    print("❌ 订阅链接不能为空")
    sys.exit(1)

if not SUB_URL.startswith(("http://", "https://")):
    print("❌ 请输入 http:// 或 https:// 开头的订阅链接")
    sys.exit(1)


# ───────────────────────────────────────────
# 获取订阅
# ───────────────────────────────────────────
print()
print("🌐 正在获取订阅...")

try:
    req = urllib.request.Request(
        SUB_URL,
        headers={
            "User-Agent": "sing-box",
            "Accept": "*/*"
        }
    )

    with urllib.request.urlopen(req, timeout=20) as resp:
        raw_content = resp.read()

except Exception as e:
    print(f"❌ 获取订阅失败: {e}")
    sys.exit(1)

if not raw_content:
    print("❌ 订阅返回内容为空")
    sys.exit(1)

print(f"✅ 订阅获取成功，共 {len(raw_content)} Bytes")

raw_content = raw_content.strip()

# 去 UTF-8 BOM
if raw_content.startswith(b"\xef\xbb\xbf"):
    raw_content = raw_content[3:]


# ───────────────────────────────────────────
# 节点列表
# ───────────────────────────────────────────
nodes = []


# ───────────────────────────────────────────
# 方法 1：解析 sing-box JSON
# ───────────────────────────────────────────
def parse_singbox_json(data):
    result = []

    try:
        text = data.decode("utf-8-sig")
        config = json.loads(text)
    except Exception:
        return result

    outbounds = config.get("outbounds", [])

    if not isinstance(outbounds, list):
        return result

    for outbound in outbounds:

        if not isinstance(outbound, dict):
            continue

        if outbound.get("type") != "anytls":
            continue

        server = outbound.get("server")
        port = outbound.get("server_port")
        password = outbound.get("password")

        if not all([server, port, password]):
            continue

        tls = outbound.get("tls") or {}

        name = outbound.get("tag") or f"{server}:{port}"

        sni = (
            tls.get("server_name")
            or server
        )

        insecure = bool(
            tls.get("insecure", False)
        )

        utls = tls.get("utls") or {}

        fingerprint = (
            utls.get("fingerprint")
            or ""
        )

        alpn = tls.get("alpn") or []

        result.append({
            "name": name,
            "server": server,
            "port": int(port),
            "password": password,
            "sni": sni,
            "insecure": insecure,
            "fingerprint": fingerprint,
            "alpn": alpn,
        })

    return result


# ───────────────────────────────────────────
# 方法 2：解析 anytls:// 链接
# ───────────────────────────────────────────
def parse_anytls_links(text):
    result = []

    for line in text.splitlines():
        line = line.strip()

        if not line:
            continue

        if not line.lower().startswith("anytls://"):
            continue

        try:
            p = urllib.parse.urlparse(line)

            params = dict(
                urllib.parse.parse_qsl(
                    p.query,
                    keep_blank_values=True
                )
            )

            password = urllib.parse.unquote(
                p.username or ""
            )

            server = p.hostname
            port = p.port

            name = (
                urllib.parse.unquote(p.fragment)
                or f"{server}:{port}"
            )

            sni = (
                params.get("sni")
                or params.get("peer")
                or params.get("serverName")
                or server
            )

            insecure = (
                params.get("insecure", "0").lower()
                in ("1", "true", "yes")
                or
                params.get("allowInsecure", "0").lower()
                in ("1", "true", "yes")
            )

            fingerprint = (
                params.get("fp")
                or params.get("fingerprint")
                or ""
            )

            alpn_raw = params.get("alpn", "")

            alpn = []

            if alpn_raw:
                alpn = [
                    x.strip()
                    for x in urllib.parse.unquote(
                        alpn_raw
                    ).split(",")
                    if x.strip()
                ]

            if not all([password, server, port]):
                continue

            result.append({
                "name": name,
                "server": server,
                "port": port,
                "password": password,
                "sni": sni,
                "insecure": insecure,
                "fingerprint": fingerprint,
                "alpn": alpn,
            })

        except Exception:
            continue

    return result


# ───────────────────────────────────────────
# 自动识别订阅类型
# ───────────────────────────────────────────
text_preview = raw_content.lstrip()

# 1. JSON
if text_preview.startswith(b"{"):

    print("📄 检测到 sing-box JSON 订阅")

    nodes = parse_singbox_json(raw_content)

# 2. 明文 anytls://
elif b"anytls://" in raw_content:

    print("📄 检测到明文 AnyTLS 订阅")

    decoded = raw_content.decode(
        "utf-8-sig",
        errors="ignore"
    )

    nodes = parse_anytls_links(decoded)

# 3. Base64
else:

    print("📄 检测到 Base64 订阅")

    try:
        b64_content = b"".join(
            raw_content.split()
        )

        padding = len(b64_content) % 4

        if padding:
            b64_content += b"=" * (4 - padding)

        decoded_bytes = base64.b64decode(
            b64_content
        )

        decoded = decoded_bytes.decode(
            "utf-8-sig",
            errors="ignore"
        )

        nodes = parse_anytls_links(decoded)

    except Exception as e:
        print(f"❌ Base64 解码失败: {e}")
        sys.exit(1)


# ───────────────────────────────────────────
# 检查解析结果
# ───────────────────────────────────────────
if not nodes:

    print()
    print("❌ 订阅中没有找到有效的 AnyTLS 节点")

    print()
    print("订阅内容前 500 个字符：")
    print("─" * 65)

    try:
        print(
            raw_content[:500].decode(
                "utf-8",
                errors="replace"
            )
        )
    except Exception:
        print(repr(raw_content[:500]))

    print("─" * 65)

    sys.exit(1)


# ───────────────────────────────────────────
# 显示节点
# ───────────────────────────────────────────
print()
print("=" * 80)
print(f"📦 共发现 {len(nodes)} 个 AnyTLS 节点")
print("=" * 80)

for i, node in enumerate(nodes, 1):

    print(
        f"{i:>3}. "
        f"{node['name']}  "
        f"[{node['server']}:{node['port']}]"
    )

print("=" * 80)


# ───────────────────────────────────────────
# 选择节点
# ───────────────────────────────────────────
while True:

    try:
        choice = input(
            "👉 请输入需要使用的节点序号: "
        ).strip()

        index = int(choice)

        if 1 <= index <= len(nodes):

            node = nodes[index - 1]
            break

        print(
            f"❌ 请输入 1 - {len(nodes)} 之间的数字"
        )

    except ValueError:
        print("❌ 请输入数字")

    except (EOFError, KeyboardInterrupt):
        print("\n❌ 已取消")
        sys.exit(1)


# ───────────────────────────────────────────
# TLS 配置
# ───────────────────────────────────────────
tls_config = {
    "enabled": True,
    "server_name": node["sni"],
    "insecure": node["insecure"]
}

if node["fingerprint"]:

    tls_config["utls"] = {
        "enabled": True,
        "fingerprint": node["fingerprint"]
    }

if node["alpn"]:

    if isinstance(node["alpn"], list):
        tls_config["alpn"] = node["alpn"]

    else:
        tls_config["alpn"] = [
            x.strip()
            for x in str(
                node["alpn"]
            ).split(",")
            if x.strip()
        ]


# ───────────────────────────────────────────
# 生成配置
# ───────────────────────────────────────────
config = {
    "log": {
        "level": "info"
    },

    "inbounds": [
        {
            "type": "socks",
            "tag": "socks-in",
            "listen": "127.0.0.1",
            "listen_port": SOCKS_PORT
        },

        {
            "type": "http",
            "tag": "http-in",
            "listen": "127.0.0.1",
            "listen_port": HTTP_PORT
        }
    ],

    "outbounds": [
        {
            "type": "anytls",
            "tag": "proxy",
            "server": node["server"],
            "server_port": node["port"],
            "password": node["password"],
            "tls": tls_config
        },

        {
            "type": "direct",
            "tag": "direct"
        }
    ],

    "route": {
        "final": "proxy"
    }
}


# ───────────────────────────────────────────
# 写入配置
# ───────────────────────────────────────────
try:

    os.makedirs(
        os.path.dirname(CONFIG_PATH),
        exist_ok=True
    )

    with open(
        CONFIG_PATH,
        "w",
        encoding="utf-8"
    ) as f:

        json.dump(
            config,
            f,
            indent=2,
            ensure_ascii=False
        )

except PermissionError:

    print(
        "❌ 无写入权限，请使用 root 用户运行"
    )

    sys.exit(1)

except Exception as e:

    print(f"❌ 写入失败: {e}")

    sys.exit(1)


# ───────────────────────────────────────────
# 显示配置
# ───────────────────────────────────────────
print()
print("=" * 65)
print("✅ 节点配置已生成")
print("=" * 65)

print(f"📌 节点名称 : {node['name']}")
print(f"🌐 服务器   : {node['server']}:{node['port']}")
print(f"🔒 SNI      : {node['sni']}")
print(f"⚠️ insecure : {node['insecure']}")
print(f"🧦 SOCKS5   : 127.0.0.1:{SOCKS_PORT}")
print(f"🌍 HTTP     : 127.0.0.1:{HTTP_PORT}")


# ───────────────────────────────────────────
# 检查 sing-box
# ───────────────────────────────────────────
try:

    version = subprocess.run(
        ["sing-box", "version"],
        capture_output=True,
        text=True
    )

except FileNotFoundError:

    print()
    print("❌ 未找到 sing-box")

    sys.exit(1)


# ───────────────────────────────────────────
# 校验配置
# ───────────────────────────────────────────
print()
print("─" * 65)
print("🔍 正在校验配置文件...")

check = subprocess.run(
    [
        "sing-box",
        "check",
        "-c",
        CONFIG_PATH
    ],
    capture_output=True,
    text=True
)

if check.returncode != 0:

    print(
        "❌ 配置校验失败，已终止重启："
    )

    print(
        check.stderr
        or check.stdout
    )

    sys.exit(1)

print("✅ 配置校验通过")


# ───────────────────────────────────────────
# 启动服务
# ───────────────────────────────────────────
print("─" * 65)
print("🔄 正在重启 sing-box 服务...")

for cmd in [
    ["systemctl", "enable", "sing-box"],
    ["systemctl", "restart", "sing-box"],
]:

    ret = subprocess.run(
        cmd,
        capture_output=True,
        text=True
    )

    label = " ".join(
        cmd[1:]
    )

    if ret.returncode == 0:

        print(
            f"✅ systemctl {label}"
        )

    else:

        print(
            f"⚠️ systemctl {label} 失败:"
        )

        print(
            ret.stderr
            or ret.stdout
        )


# ───────────────────────────────────────────
# 显示状态
# ───────────────────────────────────────────
print()
print("─" * 65)

status = subprocess.run(
    [
        "systemctl",
        "status",
        "sing-box",
        "--no-pager",
        "-l"
    ],
    capture_output=True,
    text=True
)

print(
    status.stdout
    or status.stderr
)

PYEOF

python3 /tmp/setup-singbox.py

```
脚本说明
脚本会自动完成以下操作：
1. 解析粘贴的节点链接（支持 AnyTLS 格式）
2. 生成 `/etc/sing-box/config.json`
3. 校验配置合法性
4. 重启并启用 `sing-box` 服务
导入配置
执行脚本时会遇到脚本要求粘贴节点链接
```
root@debian:~# python3 /tmp/setup-singbox.py
=======================================================
sing-box AnyTLS 一键配置工具
=======================================================
📎 请粘贴节点链接:
```
内容是在用户中心的节点列表获取，点击如图所示的复制按钮

正常输出（部分内容使用 xxx 隐去）
```
配置已写入 : /etc/sing-box/config.json
📌 节点名称   : California (美国) B26
🌐 服务器     : xxx:xxx
🔑 密码       : xxx
🔒 TLS        : True  insecure: False
🧦 SOCKS5     : 127.0.0.1:3080
🌍 HTTP 代理  : 127.0.0.1:3081

───────────────────────────────────────────────────────
🔍 正在校验配置文件...
✅ 配置校验通过
───────────────────────────────────────────────────────
🔄 正在重启 sing-box 服务...
✅ systemctl enable sing-box
✅ systemctl restart sing-box
───────────────────────────────────────────────────────
● sing-box.service - sing-box service
     Loaded: loaded (/etc/systemd/system/sing-box.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-05-14 07:56:01 UTC; 19ms ago
   Main PID: 92970 (sing-box)
      Tasks: 6 (limit: 515)
     Memory: 2.1M
        CPU: 11ms
     CGroup: /system.slice/sing-box.service
             └─92970 /usr/local/bin/sing-box run -c /etc/sing-box/config.json

May 14 07:56:01 krc01 systemd[1]: Started sing-box service.
```
此时，客户端已经在工作了，可以使用下面的命令检查工作状态
```
curl myip.ipip.net --socks5 127.0.0.1:3080
```
正常输出（根据选择节点的不同，内容将有差异，是正常情况）
```
当前 IP：154.xx.xx.xx  来自于：美国 加利福尼亚州 洛杉矶  cogentco.com
```
服务启动后将监听以下本地端口：
| **协议** | **地址** | **用途** |
| ---- |
| SOCKS5 | `127.0.0.1:3080` | 通用代理，支持 TCP/UDP |
| HTTP | `127.0.0.1:3081` | HTTP/HTTPS 代理 |
温馨提示
如何为 git wget curl 配置代理，或是你需要走代理的程序配置代理，可以询问各大 AI
## 启动与管理服务
温馨提示
下面列出的命令不需要执行，仅做说明之用
```
# 启动并设置开机自启
systemctl enable --now sing-box

# 查看运行状态
systemctl status sing-box

# 重启服务
systemctl restart sing-box

# 停止服务
systemctl stop sing-box

# 实时查看日志
journalctl -u sing-box -f

# 查看最近 100 行日志
journalctl -u sing-box -n 100 --no-pager
```
## 卸载与清理
注意
执行卸载操作将停止代理服务并删除所有相关文件，请确认不再需要后再操作。
### 第一步：停止并禁用服务
```
systemctl stop sing-box
systemctl disable sing-box
```
### 第二步：删除 systemd 服务文件
```
rm -f /etc/systemd/system/sing-box.service
systemctl daemon-reload
systemctl reset-failed
```
### 第三步：删除二进制文件与配置
```
# 删除主程序
rm -f /usr/local/bin/sing-box

# 删除配置目录（包含 config.json）
rm -rf /etc/sing-box/
```
### 第四步：清理临时脚本（如有）
```
rm -f /tmp/setup-singbox.py
```
### 验证卸载完成
```
# 确认二进制已删除
which sing-box && echo "未删除" || echo "✅ 已删除"

# 确认服务已移除
systemctl status sing-box 2>&1 | grep -q "not found" && echo "✅ 服务已移除"

# 确认配置目录已删除
[ -d /etc/sing-box ] && echo "目录仍存在" || echo "✅ 配置目录已删除"
```