#!/usr/bin/env bash
# ==============================================================================
# Sing-Box (>=1.14) + Caddy 生产加固版全自动部署脚本 (v5.1 Release)
# ==============================================================================
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
PLAIN='\033[0m'
function info() { echo -e "${GREEN}[INFO] $1${PLAIN}"; }
function warn() { echo -e "${YELLOW}[WARN] $1${PLAIN}"; }
function err() { echo -e "${RED}[ERROR] $1${PLAIN}"; exit 1; }
[[ $EUID -ne 0 ]] && err "请使用 root 权限运行此脚本!"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) SB_ARCH="amd64" ;;
aarch64) SB_ARCH="arm64" ;;
armv7l) SB_ARCH="armv7" ;;
*) err "不支持的系统架构: $ARCH" ;;
esac
clear
echo "=================================================================="
echo " Sing-Box (1.14+) + Caddy 生产加固版 (v5.1 最终发布版) "
echo "=================================================================="
echo ""
read -rp "1. 请输入解析到本 VPS 的域名 (例如: node.example.com): " DOMAIN
[[ -z "$DOMAIN" ]] && err "域名不能为空!"
read -rp "2. 请输入 H2 反代伪装路径 (默认回车: /rayh2): " H2_PATH
H2_PATH=${H2_PATH:-"/rayh2"}
[[ "$H2_PATH" != /* ]] && H2_PATH="/$H2_PATH"
read -rp "3. 请输入 gRPC 服务名 (默认回车: vless-grpc): " GRPC_SERVICE
GRPC_SERVICE=${GRPC_SERVICE:-"vless-grpc"}
read -rp "4. 请输入 Reality 监听端口 (默认回车: 8443): " REALITY_PORT
REALITY_PORT=${REALITY_PORT:-8443}
read -rp "5. 请输入 Reality 伪装域名/SNI (默认回车: addons.mozilla.org): " REALITY_DEST_DOMAIN
REALITY_DEST_DOMAIN=${REALITY_DEST_DOMAIN:-"addons.mozilla.org"}
read -rp "6. 请输入 Hysteria 2 监听端口 (UDP, 默认回车: 8444): " HY2_PORT
HY2_PORT=${HY2_PORT:-8444}
SERVER_IP=$(curl -s4m 8 ip.sb || curl -s4m 8 ifconfig.me || curl -s4m 8 api.ipify.org || echo "")
if [[ -z "$SERVER_IP" ]]; then
read -rp "未能自动获取公网 IPv4,请输入本机外网 IP: " SERVER_IP
fi
# 1. 开启 BBR
info "配置系统内核参数 (BBR + FQ)..."
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf; then
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
fi
if ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
fi
sysctl -p >/dev/null 2>&1 || true
# 2. 基础软件依赖
info "更新软件源并安装基础工具..."
apt-get update -y && apt-get install -y curl jq tar openssl socat debian-keyring debian-archive-keyring apt-transport-https
# 3. 安装 Caddy
info "安装官方 Caddy..."
curl -1sLF 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLF 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update -y && apt-get install -y caddy
# 4. 下载 Sing-Box (加固: 带超时与镜像 Fallback)
info "获取 Sing-Box 版本信息..."
LATEST_TAG=$(curl -s4m 8 https://api.github.com/repos/SagerNet/sing-box/releases/latest | jq -r .tag_name 2>/dev/null || echo "")
if [[ -z "$LATEST_TAG" || "$LATEST_TAG" == "null" ]]; then
warn "访问 GitHub API 失败或触发表率限制,启用固定默认版本 v1.14.0"
LATEST_TAG="v1.14.0"
fi
SB_VER=${LATEST_TAG#v}
DOWNLOAD_URL="https://github.com/SagerNet/sing-box/releases/download/${LATEST_TAG}/sing-box-${SB_VER}-linux-${SB_ARCH}.tar.gz"
MIRROR_URL="https://ghfast.top/${DOWNLOAD_URL}"
info "正在下载 Sing-Box $LATEST_TAG..."
if ! curl -Lo /tmp/sing-box.tar.gz --fail -m 180 --connect-timeout 15 "$DOWNLOAD_URL"; then
warn "官方 GitHub Releases 下载超时/失败,正在尝试通过镜像源加速下载..."
curl -Lo /tmp/sing-box.tar.gz --fail -m 180 --connect-timeout 15 "$MIRROR_URL" || err "Sing-box 核心下载失败,请检查网络!"
fi
tar -xzf /tmp/sing-box.tar.gz -C /tmp
mv /tmp/sing-box-${SB_VER}-linux-${SB_ARCH}/sing-box /usr/local/bin/sing-box
chmod +x /usr/local/bin/sing-box
rm -rf /tmp/sing-box*
# 5. 生成凭证并落盘真实主域名
info "生成各协议独立 UUID 及凭证..."
UUID_H2=$(cat /proc/sys/kernel/random/uuid)
UUID_GRPC=$(cat /proc/sys/kernel/random/uuid)
UUID_REALITY=$(cat /proc/sys/kernel/random/uuid)
HY2_PASSWORD=$(openssl rand -base64 20 | tr -dc 'a-zA-Z0-9')
H2_PORT=10001
GRPC_PORT=10002
KEY_PAIR=$(/usr/local/bin/sing-box generate reality-keypair)
REALITY_PRIVATE_KEY=$(echo "$KEY_PAIR" | grep -i "PrivateKey:" | awk '{print $2}')
REALITY_PUBLIC_KEY=$(echo "$KEY_PAIR" | grep -i "PublicKey:" | awk '{print $2}')
REALITY_SHORT_ID=$(openssl rand -hex 8)
mkdir -p /etc/sing-box /etc/sing-box/cert
echo "$DOMAIN" > /etc/sing-box/domain.txt
chmod 600 /etc/sing-box/domain.txt
# 6. 伪装网页
mkdir -p /var/www/html
cat << 'EOF' > /var/www/html/index.html
Edge Acceleration & Cloud Services
Cloud Delivery Gateway
This edge cluster provides global asset distribution, caching optimizations, and routing infrastructure.
● All Systems Operational
EOF
# 7. 配置 Caddy
cat << EOF > /etc/caddy/Caddyfile
$DOMAIN {
root * /var/www/html
file_server
@grpc {
header Content-Type application/grpc*
path /$GRPC_SERVICE/*
}
reverse_proxy @grpc 127.0.0.1:$GRPC_PORT {
transport http {
versions h2c 2
}
}
@h2 {
path $H2_PATH
}
reverse_proxy @h2 127.0.0.1:$H2_PORT {
transport http {
versions h2c 2
}
}
}
EOF
systemctl daemon-reload
systemctl enable --now caddy
systemctl restart caddy
# 8. 轮询证书 (120 秒 + 兜底注入 allowInsecure)
CERT_DIR="/etc/sing-box/cert"
info "等待 Caddy 申请正式 SSL 证书 (最长 120 秒)..."
CADDY_CERT_FOUND=false
TIMEOUT=120
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
CERT_FILE=$(find /var/lib/caddy/.local/share/caddy/certificates -type f -name "$DOMAIN.crt" 2>/dev/null | head -n 1)
KEY_FILE=$(find /var/lib/caddy/.local/share/caddy/certificates -type f -name "$DOMAIN.key" 2>/dev/null | head -n 1)
if [[ -n "$CERT_FILE" && -n "$KEY_FILE" && -s "$CERT_FILE" && -s "$KEY_FILE" ]]; then
CADDY_CERT_FOUND=true
break
fi
printf "\r[INFO] 正在申请/等待证书: 已耗时 %ds / %ds..." "$ELAPSED" "$TIMEOUT"
sleep 2
ELAPSED=$((ELAPSED + 2))
done
echo ""
if [[ "$CADDY_CERT_FOUND" == "true" ]]; then
info "成功获取 Caddy 正式证书,同步至 Sing-Box..."
cp -L "$CERT_FILE" "$CERT_DIR/server.crt"
cp -L "$KEY_FILE" "$CERT_DIR/server.key"
H2_INSECURE_PARAM=""
GRPC_INSECURE_PARAM=""
HY2_INSECURE_PARAM=""
CERT_STATUS_STR="已挂载正规正式证书 (严格校验抗 MITM)"
else
warn "120 秒内未捕获到 Caddy 证书,启动自签名证书兜底!"
warn "受影响节点链接将自动开启 allowInsecure 确保连通性。"
openssl req -x509 -nodes -newkey ec:<(openssl ecparam -name prime256v1) \
-keyout "$CERT_DIR/server.key" \
-out "$CERT_DIR/server.crt" \
-days 3650 \
-subj "/CN=$DOMAIN" \
-addext "subjectAltName=DNS:$DOMAIN" >/dev/null 2>&1
H2_INSECURE_PARAM="&allowInsecure=1"
GRPC_INSECURE_PARAM="&allowInsecure=1"
HY2_INSECURE_PARAM="&insecure=1"
CERT_STATUS_STR="注意: 使用自签名证书兜底 (后续可通过 sb-sync-cert 升级为正式证书)"
fi
# 9. 生成配置文件
cat << EOF > /etc/sing-box/config.json
{
"log": {
"level": "warn",
"timestamp": true
},
"inbounds": [
{
"type": "vless",
"tag": "vless-h2-in",
"listen": "127.0.0.1",
"listen_port": $H2_PORT,
"users": [
{
"uuid": "$UUID_H2",
"flow": ""
}
],
"transport": {
"type": "http",
"host": [
"$DOMAIN"
],
"path": "$H2_PATH"
}
},
{
"type": "vless",
"tag": "vless-grpc-in",
"listen": "127.0.0.1",
"listen_port": $GRPC_PORT,
"users": [
{
"uuid": "$UUID_GRPC",
"flow": ""
}
],
"transport": {
"type": "grpc",
"service_name": "$GRPC_SERVICE"
}
},
{
"type": "vless",
"tag": "vless-reality-in",
"listen": "::",
"listen_port": $REALITY_PORT,
"users": [
{
"uuid": "$UUID_REALITY",
"flow": "xtls-rprx-vision"
}
],
"tls": {
"enabled": true,
"server_name": "$REALITY_DEST_DOMAIN",
"reality": {
"enabled": true,
"handshake": {
"server": "$REALITY_DEST_DOMAIN",
"server_port": 443
},
"private_key": "$REALITY_PRIVATE_KEY",
"short_id": [
"$REALITY_SHORT_ID"
]
}
}
},
{
"type": "hysteria2",
"tag": "hy2-in",
"listen": "::",
"listen_port": $HY2_PORT,
"users": [
{
"password": "$HY2_PASSWORD"
}
],
"ignore_client_bandwidth": false,
"tls": {
"enabled": true,
"server_name": "$DOMAIN",
"certificate_path": "$CERT_DIR/server.crt",
"key_path": "$CERT_DIR/server.key"
}
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
]
}
EOF
# 预检语法
info "正在执行 Sing-Box 配置语法严格预检..."
if ! /usr/local/bin/sing-box check -c /etc/sing-box/config.json; then
err "Sing-Box 配置文件校验失败!已中断部署,请检查配置内容。"
fi
info "配置语法校验通过!"
# 10. 创建 systemd 服务
cat << EOF > /etc/systemd/system/sing-box.service
[Unit]
Description=sing-box service
Documentation=https://sing-box.sagernet.org
After=network.target nss-lookup.target
[Service]
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=10s
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now sing-box
systemctl restart sing-box
# 注册 sb-sync-cert 证书热更新命令 (修复: 直接读取 domain.txt)
cat << 'EOF' > /usr/local/bin/sb-sync-cert
#!/usr/bin/env bash
set -e
DOMAIN_FILE="/etc/sing-box/domain.txt"
if [[ ! -f "$DOMAIN_FILE" ]]; then
echo -e "\033[31m[ERROR] 未找到 $DOMAIN_FILE,无法确定主域名!\033[0m"
exit 1
fi
DOMAIN=$(tr -d '\r\n ' < "$DOMAIN_FILE")
[[ -z "$DOMAIN" ]] && { echo -e "\033[31m[ERROR] 域名配置为空!\033[0m"; exit 1; }
echo "[INFO] 正在从 Caddy 存储目录检索 $DOMAIN 的正规证书..."
CERT_FILE=$(find /var/lib/caddy/.local/share/caddy/certificates -type f -name "$DOMAIN.crt" 2>/dev/null | head -n 1)
KEY_FILE=$(find /var/lib/caddy/.local/share/caddy/certificates -type f -name "$DOMAIN.key" 2>/dev/null | head -n 1)
if [[ -n "$CERT_FILE" && -n "$KEY_FILE" && -s "$CERT_FILE" && -s "$KEY_FILE" ]]; then
cp -L "$CERT_FILE" /etc/sing-box/cert/server.crt
cp -L "$KEY_FILE" /etc/sing-box/cert/server.key
chmod 644 /etc/sing-box/cert/server.crt
chmod 600 /etc/sing-box/cert/server.key
systemctl restart sing-box
echo -e "\033[32m[SUCCESS] 正规证书已成功同步至 Sing-Box,服务已重启!客户端请关闭 allowInsecure/insecure 开关。\033[0m"
else
echo -e "\033[31m[FAILED] Caddy 证书目录暂未发现 $DOMAIN 的正式证书,请检查 Caddy 申请状态 (journalctl -u caddy -f)。\033[0m"
fi
EOF
chmod +x /usr/local/bin/sb-sync-cert
# 加固权限
chmod 700 /etc/sing-box
chmod 700 /etc/sing-box/cert
chmod 600 /etc/sing-box/cert/server.key
chmod 644 /etc/sing-box/cert/server.crt
chmod 600 /etc/sing-box/config.json
# 拼接节点链接
LINK_HY2="hysteria2://${HY2_PASSWORD}@${DOMAIN}:${HY2_PORT}/?sni=${DOMAIN}${HY2_INSECURE_PARAM}#Hy2-${DOMAIN}"
LINK_REALITY="vless://${UUID_REALITY}@${SERVER_IP}:${REALITY_PORT}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${REALITY_DEST_DOMAIN}&fp=chrome&pbk=${REALITY_PUBLIC_KEY}&sid=${REALITY_SHORT_ID}&type=tcp#Reality-${REALITY_DEST_DOMAIN}"
LINK_H2="vless://${UUID_H2}@${DOMAIN}:443?encryption=none&security=tls&sni=${DOMAIN}&type=http&path=$(echo -n "$H2_PATH" | jq -sRr @uri)&host=${DOMAIN}${H2_INSECURE_PARAM}#${DOMAIN}-H2"
LINK_GRPC="vless://${UUID_GRPC}@${DOMAIN}:443?encryption=none&security=tls&sni=${DOMAIN}&type=grpc&serviceName=${GRPC_SERVICE}${GRPC_INSECURE_PARAM}#${DOMAIN}-gRPC"
INFO_FILE="/etc/sing-box/node_info.txt"
cat << EOF > "$INFO_FILE"
==================================================================
Sing-Box 节点配置与连接信息 (生产加固版)
==================================================================
主域名 (SNI): $DOMAIN
VPS 公网 IP: $SERVER_IP
H2 UUID (独立): $UUID_H2
gRPC UUID (独立): $UUID_GRPC
Reality UUID (独立):$UUID_REALITY
Hy2 强密码: $HY2_PASSWORD
SSL 证书状态: $CERT_STATUS_STR
Hy2 端口 (UDP): $HY2_PORT
Reality 端口 (TCP): $REALITY_PORT
Reality 伪装 SNI: $REALITY_DEST_DOMAIN
Reality 公钥: $REALITY_PUBLIC_KEY
Reality Short ID: $REALITY_SHORT_ID
------------------------------------------------------------------
[1] VLESS + Reality 节点 (主力首选 / 直连低延迟):
$LINK_REALITY
[2] Hysteria 2 节点 (UDP加速 / 晚高峰与拥塞优选):
$LINK_HY2
[3] VLESS + H2 节点 (Caddy TLS 443 伪装反代):
$LINK_H2
[4] VLESS + gRPC 节点 (Caddy TLS 443 伪装反代):
$LINK_GRPC
==================================================================
EOF
chmod 600 "$INFO_FILE"
clear
cat "$INFO_FILE"
echo ""
echo -e "${GREEN}配置已保存至文件: ${INFO_FILE} (权限已严格锁定为 600)${PLAIN}"
echo "若使用了自签证书兜底,待域名生效后随时运行 sb-sync-cert 即可升级正规证书!"