#!/usr/bin/env bash set -euo pipefail # ----------------------- # 彩色输出函数 info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } err() { echo -e "\033[1;31m[ERR]\033[0m $*" >&2; } # ----------------------- # 检测系统类型 detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release OS_ID="${ID:-}" OS_ID_LIKE="${ID_LIKE:-}" else OS_ID="" OS_ID_LIKE="" fi if echo "$OS_ID $OS_ID_LIKE" | grep -qi "alpine"; then OS="alpine" elif echo "$OS_ID $OS_ID_LIKE" | grep -Ei "debian|ubuntu" >/dev/null; then OS="debian" elif echo "$OS_ID $OS_ID_LIKE" | grep -Ei "centos|rhel|fedora" >/dev/null; then OS="redhat" else OS="unknown" fi } detect_os info "检测到系统: $OS (${OS_ID:-unknown})" # ----------------------- # 检查 root 权限 check_root() { if [ "$(id -u)" != "0" ]; then err "此脚本需要 root 权限" err "请使用: sudo bash -c \"\$(curl -fsSL ...)\" 或切换到 root 用户" exit 1 fi } check_root # ----------------------- # 安装依赖 install_deps() { info "安装系统依赖..." case "$OS" in alpine) apk update || { err "apk update 失败"; exit 1; } apk add --no-cache bash curl ca-certificates openssl openrc jq || { err "依赖安装失败" exit 1 } ;; debian) export DEBIAN_FRONTEND=noninteractive apt-get update -y || { err "apt update 失败"; exit 1; } apt-get install -y curl ca-certificates openssl jq || { err "依赖安装失败" exit 1 } ;; redhat) yum install -y curl ca-certificates openssl jq || { err "依赖安装失败" exit 1 } ;; *) warn "未识别的系统类型,尝试继续..." ;; esac info "依赖安装完成" } install_deps # ----------------------- # 工具函数 # 生成随机端口 rand_port() { local port port=$(shuf -i 10000-60000 -n 1 2>/dev/null) || port=$((RANDOM % 50001 + 10000)) echo "$port" } # 生成随机密码 rand_pass() { local pass pass=$(openssl rand -base64 16 2>/dev/null | tr -d '\n\r') || pass=$(head -c 16 /dev/urandom | base64 2>/dev/null | tr -d '\n\r') echo "$pass" } # 生成UUID rand_uuid() { local uuid if [ -f /proc/sys/kernel/random/uuid ]; then uuid=$(cat /proc/sys/kernel/random/uuid) else uuid=$(openssl rand -hex 16 | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-\9\10-\11\12\13\14\15\16/') fi echo "$uuid" } # ----------------------- # 配置节点名称后缀 echo "请输入节点名称(留空则默认议名):" read -r user_name if [[ -n "$user_name" ]]; then suffix="-${user_name}" echo "$suffix" > /root/node_names.txt else suffix="" fi # ----------------------- # 选择要部署的协议 select_protocols() { info "=== 选择要部署的协议 ===" echo "1) Shadowsocks (SS)" echo "2) Hysteria2 (HY2)" echo "3) TUIC" echo "4) VLESS Reality" echo "5) AnyTLS Reality" echo "" echo "请输入要部署的协议编号(多个用空格分隔,如: 1 2 4):" read -r protocol_input # 使用全局变量 ENABLE_SS=false ENABLE_HY2=false ENABLE_TUIC=false ENABLE_REALITY=false ENABLE_ANYTLS=false for num in $protocol_input; do case "$num" in 1) ENABLE_SS=true ;; 2) ENABLE_HY2=true ;; 3) ENABLE_TUIC=true ;; 4) ENABLE_REALITY=true ;; 5) ENABLE_ANYTLS=true ;; *) warn "无效选项: $num" ;; esac done if ! $ENABLE_SS && ! $ENABLE_HY2 && ! $ENABLE_TUIC && ! $ENABLE_REALITY && ! $ENABLE_ANYTLS; then err "未选择任何协议,退出安装" exit 1 fi # 保存协议选择到文件(确保持久化) mkdir -p /etc/sing-box cat > /etc/sing-box/.protocols < /etc/sing-box/.config_cache.tmp || true echo "REALITY_SNI=$REALITY_SNI" >> /etc/sing-box/.config_cache.tmp || true # 保留其他可能已有的缓存条目(若存在老的 .config_cache),把新临时与旧文件合并(保新值覆盖旧值) if [ -f /etc/sing-box/.config_cache ]; then # 将旧文件中不在新文件内的行追加 awk 'FNR==NR{a[$1]=1;next} {split($0,k,"="); if(!(k[1] in a)) print $0}' /etc/sing-box/.config_cache.tmp /etc/sing-box/.config_cache >> /etc/sing-box/.config_cache.tmp2 || true mv /etc/sing-box/.config_cache.tmp2 /etc/sing-box/.config_cache.tmp || true fi mv /etc/sing-box/.config_cache.tmp /etc/sing-box/.config_cache || true # ----------------------- # 生成随机端口 rand_port() { shuf -i 10000-60000 -n 1 2>/dev/null || echo $((RANDOM % 50001 + 10000)) } # 生成随机密码 rand_pass() { openssl rand -base64 16 | tr -d '\n\r' || head -c 16 /dev/urandom | base64 | tr -d '\n\r' } # 生成UUID rand_uuid() { cat /proc/sys/kernel/random/uuid } # ----------------------- # 配置端口和密码 get_config() { info "开始配置端口和密码..." if $ENABLE_SS; then info "=== 配置 Shadowsocks (SS) ===" if [ -n "${SINGBOX_PORT_SS:-}" ]; then PORT_SS="$SINGBOX_PORT_SS" else read -p "请输入 SS 端口(留空则随机 10000-60000): " USER_PORT_SS PORT_SS="${USER_PORT_SS:-$(rand_port)}" fi PSK_SS=$(rand_pass) info "SS 端口: $PORT_SS" info "SS 加密方式: $SS_METHOD" info "SS 密码已自动生成" fi if $ENABLE_HY2; then info "=== 配置 Hysteria2 (HY2) ===" if [ -n "${SINGBOX_PORT_HY2:-}" ]; then PORT_HY2="$SINGBOX_PORT_HY2" else read -p "请输入 HY2 端口(留空则随机 10000-60000): " USER_PORT_HY2 PORT_HY2="${USER_PORT_HY2:-$(rand_port)}" fi PSK_HY2=$(rand_pass) info "HY2 端口: $PORT_HY2" info "HY2 密码已自动生成" fi if $ENABLE_TUIC; then info "=== 配置 TUIC ===" if [ -n "${SINGBOX_PORT_TUIC:-}" ]; then PORT_TUIC="$SINGBOX_PORT_TUIC" else read -p "请输入 TUIC 端口(留空则随机 10000-60000): " USER_PORT_TUIC PORT_TUIC="${USER_PORT_TUIC:-$(rand_port)}" fi PSK_TUIC=$(rand_pass) UUID_TUIC=$(rand_uuid) info "TUIC 端口: $PORT_TUIC" info "TUIC UUID 和密码已自动生成" fi if $ENABLE_REALITY; then info "=== 配置 VLESS Reality ===" if [ -n "${SINGBOX_PORT_REALITY:-}" ]; then PORT_REALITY="$SINGBOX_PORT_REALITY" else read -p "请输入 VLESS Reality 端口(留空则随机 10000-60000): " USER_PORT_REALITY PORT_REALITY="${USER_PORT_REALITY:-$(rand_port)}" fi UUID=$(rand_uuid) info "VLESS Reality 端口: $PORT_REALITY" info "VLESS Reality UUID 已自动生成" fi if $ENABLE_ANYTLS; then info "=== 配置 AnyTLS Reality ===" if [ -n "${SINGBOX_PORT_ANYTLS:-}" ]; then PORT_ANYTLS="$SINGBOX_PORT_ANYTLS" else read -p "请输入 AnyTLS Reality 端口(留空则随机 10000-60000): " USER_PORT_ANYTLS PORT_ANYTLS="${USER_PORT_ANYTLS:-$(rand_port)}" fi ANYTLS_USER=$(openssl rand -hex 4) ANYTLS_PSK=$(openssl rand -base64 16) info "AnyTLS Reality 端口: $PORT_ANYTLS" info "AnyTLS Reality 用户名: $ANYTLS_USER" info "AnyTLS Reality 密码已自动生成" fi info "配置完成,继续安装..." } get_config # ----------------------- # 安装 sing-box install_singbox() { info "开始安装 sing-box..." if command -v sing-box >/dev/null 2>&1; then CURRENT_VERSION=$(sing-box version 2>/dev/null | head -1 || echo "unknown") warn "检测到已安装 sing-box: $CURRENT_VERSION" read -p "是否重新安装?(y/N): " REINSTALL if [[ ! "$REINSTALL" =~ ^[Yy]$ ]]; then info "跳过 sing-box 安装" return 0 fi fi case "$OS" in alpine) info "使用 Edge 仓库安装 sing-box" apk update || { err "apk update 失败"; exit 1; } apk add --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community sing-box || { err "sing-box 安装失败" exit 1 } ;; debian|redhat) bash <(curl -fsSL https://sing-box.app/install.sh) || { err "sing-box 安装失败" exit 1 } ;; *) err "未支持的系统,无法安装 sing-box" exit 1 ;; esac if ! command -v sing-box >/dev/null 2>&1; then err "sing-box 安装后未找到可执行文件" exit 1 fi INSTALLED_VERSION=$(sing-box version 2>/dev/null | head -1 || echo "unknown") info "sing-box 安装成功: $INSTALLED_VERSION" } install_singbox # ----------------------- # 生成 Reality 密钥对(必须在 sing-box 安装之后) generate_reality_keys() { if ! $ENABLE_REALITY && ! $ENABLE_ANYTLS; then info "跳过 Reality 密钥生成(未选择 Reality 协议)" return 0 fi info "生成 Reality 密钥对..." if ! command -v sing-box >/dev/null 2>&1; then err "sing-box 未安装,无法生成 Reality 密钥" exit 1 fi REALITY_KEYS=$(sing-box generate reality-keypair 2>&1) || { err "生成 Reality 密钥失败" exit 1 } REALITY_PK=$(echo "$REALITY_KEYS" | grep "PrivateKey" | awk '{print $NF}' | tr -d '\r') REALITY_PUB=$(echo "$REALITY_KEYS" | grep "PublicKey" | awk '{print $NF}' | tr -d '\r') REALITY_SID=$(sing-box generate rand 8 --hex 2>&1) || { err "生成 Reality ShortID 失败" exit 1 } if [ -z "$REALITY_PK" ] || [ -z "$REALITY_PUB" ] || [ -z "$REALITY_SID" ]; then err "Reality 密钥生成结果为空" exit 1 fi mkdir -p /etc/sing-box echo -n "$REALITY_PUB" > /etc/sing-box/.reality_pub echo -n "$REALITY_SID" > /etc/sing-box/.reality_sid info "Reality 密钥已生成" } generate_reality_keys # ----------------------- # 生成 HY2/TUIC 自签证书(仅在需要时) generate_cert() { if ! $ENABLE_HY2 && ! $ENABLE_TUIC; then info "跳过证书生成(未选择 HY2 或 TUIC)" return 0 fi info "生成 HY2/TUIC 自签证书..." mkdir -p /etc/sing-box/certs if [ ! -f /etc/sing-box/certs/fullchain.pem ] || [ ! -f /etc/sing-box/certs/privkey.pem ]; then openssl req -x509 -newkey rsa:2048 -nodes \ -keyout /etc/sing-box/certs/privkey.pem \ -out /etc/sing-box/certs/fullchain.pem \ -days 3650 \ -subj "/CN=www.bing.com" || { err "证书生成失败" exit 1 } info "证书已生成" else info "证书已存在" fi } generate_cert # ----------------------- # 生成配置文件 CONFIG_PATH="/etc/sing-box/config.json" create_config() { info "生成配置文件: $CONFIG_PATH" mkdir -p "$(dirname "$CONFIG_PATH")" # 构建 inbounds 内容(使用临时文件避免字符串处理问题) local TEMP_INBOUNDS="/tmp/singbox_inbounds_$.json" > "$TEMP_INBOUNDS" local need_comma=false if $ENABLE_SS; then cat >> "$TEMP_INBOUNDS" <<'INBOUND_SS' { "type": "shadowsocks", "listen": "::", "listen_port": PORT_SS_PLACEHOLDER, "method": "METHOD_SS_PLACEHOLDER", "password": "PSK_SS_PLACEHOLDER", "tag": "ss-in" } INBOUND_SS sed -i "s|PORT_SS_PLACEHOLDER|$PORT_SS|g" "$TEMP_INBOUNDS" sed -i "s|METHOD_SS_PLACEHOLDER|$SS_METHOD|g" "$TEMP_INBOUNDS" sed -i "s|PSK_SS_PLACEHOLDER|$PSK_SS|g" "$TEMP_INBOUNDS" need_comma=true fi if $ENABLE_HY2; then $need_comma && echo "," >> "$TEMP_INBOUNDS" cat >> "$TEMP_INBOUNDS" <<'INBOUND_HY2' { "type": "hysteria2", "tag": "hy2-in", "listen": "::", "listen_port": PORT_HY2_PLACEHOLDER, "users": [ { "password": "PSK_HY2_PLACEHOLDER" } ], "tls": { "enabled": true, "alpn": ["h3"], "certificate_path": "/etc/sing-box/certs/fullchain.pem", "key_path": "/etc/sing-box/certs/privkey.pem" } } INBOUND_HY2 sed -i "s|PORT_HY2_PLACEHOLDER|$PORT_HY2|g" "$TEMP_INBOUNDS" sed -i "s|PSK_HY2_PLACEHOLDER|$PSK_HY2|g" "$TEMP_INBOUNDS" need_comma=true fi if $ENABLE_TUIC; then $need_comma && echo "," >> "$TEMP_INBOUNDS" cat >> "$TEMP_INBOUNDS" <<'INBOUND_TUIC' { "type": "tuic", "tag": "tuic-in", "listen": "::", "listen_port": PORT_TUIC_PLACEHOLDER, "users": [ { "uuid": "UUID_TUIC_PLACEHOLDER", "password": "PSK_TUIC_PLACEHOLDER" } ], "congestion_control": "bbr", "tls": { "enabled": true, "alpn": ["h3"], "certificate_path": "/etc/sing-box/certs/fullchain.pem", "key_path": "/etc/sing-box/certs/privkey.pem" } } INBOUND_TUIC sed -i "s|PORT_TUIC_PLACEHOLDER|$PORT_TUIC|g" "$TEMP_INBOUNDS" sed -i "s|UUID_TUIC_PLACEHOLDER|$UUID_TUIC|g" "$TEMP_INBOUNDS" sed -i "s|PSK_TUIC_PLACEHOLDER|$PSK_TUIC|g" "$TEMP_INBOUNDS" need_comma=true fi if $ENABLE_REALITY; then $need_comma && echo "," >> "$TEMP_INBOUNDS" cat >> "$TEMP_INBOUNDS" <<'INBOUND_REALITY' { "type": "vless", "tag": "vless-in", "listen": "::", "listen_port": PORT_REALITY_PLACEHOLDER, "users": [ { "uuid": "UUID_REALITY_PLACEHOLDER", "flow": "xtls-rprx-vision" } ], "tls": { "enabled": true, "server_name": "REALITY_SNI_PLACEHOLDER", "reality": { "enabled": true, "handshake": { "server": "REALITY_SNI_PLACEHOLDER", "server_port": 443 }, "private_key": "REALITY_PK_PLACEHOLDER", "short_id": ["REALITY_SID_PLACEHOLDER"] } } } INBOUND_REALITY sed -i "s|PORT_REALITY_PLACEHOLDER|$PORT_REALITY|g" "$TEMP_INBOUNDS" sed -i "s|UUID_REALITY_PLACEHOLDER|$UUID|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_PK_PLACEHOLDER|$REALITY_PK|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_SID_PLACEHOLDER|$REALITY_SID|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_SNI_PLACEHOLDER|$REALITY_SNI|g" "$TEMP_INBOUNDS" need_comma=true fi if $ENABLE_ANYTLS; then $need_comma && echo "," >> "$TEMP_INBOUNDS" cat >> "$TEMP_INBOUNDS" <<'INBOUND_ANYTLS' { "type": "anytls", "tag": "anytls-in", "listen": "::", "listen_port": PORT_ANYTLS_PLACEHOLDER, "users": [ { "name": "ANYTLS_USER_PLACEHOLDER", "password": "ANYTLS_PSK_PLACEHOLDER" } ], "padding_scheme": [], "tls": { "enabled": true, "server_name": "REALITY_SNI_PLACEHOLDER", "reality": { "enabled": true, "handshake": { "server": "REALITY_SNI_PLACEHOLDER", "server_port": 443 }, "private_key": "REALITY_PK_PLACEHOLDER", "short_id": [ "REALITY_SID_PLACEHOLDER" ] } } } INBOUND_ANYTLS sed -i "s|PORT_ANYTLS_PLACEHOLDER|$PORT_ANYTLS|g" "$TEMP_INBOUNDS" sed -i "s|ANYTLS_USER_PLACEHOLDER|$ANYTLS_USER|g" "$TEMP_INBOUNDS" sed -i "s|ANYTLS_PSK_PLACEHOLDER|$ANYTLS_PSK|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_PK_PLACEHOLDER|$REALITY_PK|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_SID_PLACEHOLDER|$REALITY_SID|g" "$TEMP_INBOUNDS" sed -i "s|REALITY_SNI_PLACEHOLDER|$REALITY_SNI|g" "$TEMP_INBOUNDS" need_comma=true fi # 生成最终配置 cat > "$CONFIG_PATH" <<'CONFIG_HEAD' { "log": { "level": "info", "timestamp": true }, "ntp": { "enabled": true, "server": "time.apple.com", "server_port": 123, "interval": "30m" }, "inbounds": [ CONFIG_HEAD cat "$TEMP_INBOUNDS" >> "$CONFIG_PATH" cat >> "$CONFIG_PATH" <<'CONFIG_TAIL' ], "outbounds": [ { "type": "direct", "tag": "direct-out" } ] } CONFIG_TAIL rm -f "$TEMP_INBOUNDS" sing-box check -c "$CONFIG_PATH" >/dev/null 2>&1 \ && info "配置文件验证通过" \ || warn "配置文件验证失败,但继续执行" # 保存配置缓存(追加/覆盖) cat > /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache <> /etc/sing-box/.config_cache info "配置缓存已保存到 /etc/sing-box/.config_cache" } # 调用配置生成 create_config info "配置生成完成,准备设置服务..." # ----------------------- # 设置服务 setup_service() { info "配置系统服务..." if [ "$OS" = "alpine" ]; then SERVICE_PATH="/etc/init.d/sing-box" cat > "$SERVICE_PATH" <<'OPENRC' #!/sbin/openrc-run name="sing-box" description="Sing-box Proxy Server" command="/usr/bin/sing-box" command_args="run -c /etc/sing-box/config.json" pidfile="/run/${RC_SVCNAME}.pid" command_background="yes" output_log="/var/log/sing-box.log" error_log="/var/log/sing-box.err" # 自动拉起(程序崩溃、OOM、被 kill 后自动恢复) supervisor=supervise-daemon supervise_daemon_args="--respawn-max 0 --respawn-delay 5" depend() { need net after firewall } start_pre() { checkpath --directory --mode 0755 /var/log checkpath --directory --mode 0755 /run } OPENRC chmod +x "$SERVICE_PATH" rc-update add sing-box default >/dev/null 2>&1 || warn "添加开机自启失败" rc-service sing-box restart || { err "服务启动失败" tail -20 /var/log/sing-box.err 2>/dev/null || tail -20 /var/log/sing-box.log 2>/dev/null || true exit 1 } sleep 2 if rc-service sing-box status >/dev/null 2>&1; then info "✅ OpenRC 服务已启动" else err "服务状态异常" exit 1 fi else SERVICE_PATH="/etc/systemd/system/sing-box.service" cat > "$SERVICE_PATH" <<'SYSTEMD' [Unit] Description=Sing-box Proxy Server Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target Wants=network.target [Service] Type=simple User=root WorkingDirectory=/etc/sing-box ExecStart=/usr/bin/sing-box run -c /etc/sing-box/config.json ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=10s LimitNOFILE=1048576 [Install] WantedBy=multi-user.target SYSTEMD systemctl daemon-reload systemctl enable sing-box >/dev/null 2>&1 systemctl restart sing-box || { err "服务启动失败" journalctl -u sing-box -n 30 --no-pager exit 1 } sleep 2 if systemctl is-active sing-box >/dev/null 2>&1; then info "✅ Systemd 服务已启动" else err "服务状态异常" exit 1 fi fi info "服务配置完成: $SERVICE_PATH" } setup_service # ----------------------- # 获取公网 IP get_public_ip() { local ip="" for url in \ "https://api.ipify.org" \ "https://ipinfo.io/ip" \ "https://ifconfig.me" \ "https://icanhazip.com" \ "https://ipecho.net/plain"; do ip=$(curl -s --max-time 5 "$url" 2>/dev/null | tr -d '[:space:]' || true) if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "$ip" return 0 fi done return 1 } # 如果用户提供了 CUSTOM_IP,则优先使用;否则自动检测出口 IP if [ -n "${CUSTOM_IP:-}" ]; then PUB_IP="$CUSTOM_IP" info "使用用户提供的连接IP或ddns域名 : $PUB_IP" else PUB_IP=$(get_public_ip || echo "YOUR_SERVER_IP") if [ "$PUB_IP" = "YOUR_SERVER_IP" ]; then warn "无法获取公网 IP,请手动替换" else info "检测到公网 IP: $PUB_IP" fi fi # ----------------------- # 生成链接(仅生成已选择的协议) generate_uris() { local host="$PUB_IP" if $ENABLE_SS; then local ss_userinfo="${SS_METHOD}:${PSK_SS}" ss_encoded=$(printf "%s" "$ss_userinfo" | sed 's/:/%3A/g; s/+/%2B/g; s/\//%2F/g; s/=/%3D/g') ss_b64=$(printf "%s" "$ss_userinfo" | base64 -w0 2>/dev/null || printf "%s" "$ss_userinfo" | base64 | tr -d '\n') echo "=== Shadowsocks (SS) ===" echo "ss://${ss_encoded}@${host}:${PORT_SS}#ss${suffix}" echo "ss://${ss_b64}@${host}:${PORT_SS}#ss${suffix}" echo "" fi if $ENABLE_HY2; then hy2_encoded=$(printf "%s" "$PSK_HY2" | sed 's/:/%3A/g; s/+/%2B/g; s/\//%2F/g; s/=/%3D/g') echo "=== Hysteria2 (HY2) ===" echo "hy2://${hy2_encoded}@${host}:${PORT_HY2}/?sni=www.bing.com&alpn=h3&insecure=1#hy2${suffix}" echo "" fi if $ENABLE_TUIC; then tuic_encoded=$(printf "%s" "$PSK_TUIC" | sed 's/:/%3A/g; s/+/%2B/g; s/\//%2F/g; s/=/%3D/g') echo "=== TUIC ===" echo "tuic://${UUID_TUIC}:${tuic_encoded}@${host}:${PORT_TUIC}/?congestion_control=bbr&alpn=h3&sni=www.bing.com&insecure=1#tuic${suffix}" echo "" fi if $ENABLE_REALITY; then echo "=== VLESS Reality ===" echo "vless://${UUID}@${host}:${PORT_REALITY}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${REALITY_SNI}&fp=chrome&pbk=${REALITY_PUB}&sid=${REALITY_SID}#reality${suffix}" echo "" fi if $ENABLE_ANYTLS; then anytls_user_encoded=$(printf "%s" "$ANYTLS_USER" | sed 's/:/%3A/g; s/+/%2B/g; s/\//%2F/g; s/=/%3D/g') anytls_pass_encoded=$(printf "%s" "$ANYTLS_PSK" | sed 's/:/%3A/g; s/+/%2B/g; s/\//%2F/g; s/=/%3D/g') echo "=== AnyTLS Reality ===" echo "anytls://${anytls_pass_encoded}@${host}:${PORT_ANYTLS}/?security=reality&sni=${REALITY_SNI}&fp=chrome&pbk=${REALITY_PUB}&sid=${REALITY_SID}#anytls${suffix}" echo "" fi } # ----------------------- # 最终输出 echo "" echo "==========================================" info "🎉 Sing-box 部署完成!" echo "==========================================" echo "" info "📋 配置信息:" $ENABLE_SS && echo " SS 端口: $PORT_SS | 密码: $PSK_SS | 加密: $SS_METHOD" $ENABLE_HY2 && echo " HY2 端口: $PORT_HY2 | 密码: $PSK_HY2" $ENABLE_TUIC && echo " TUIC 端口: $PORT_TUIC | UUID: $UUID_TUIC | 密码: $PSK_TUIC" $ENABLE_REALITY && echo " Reality 端口: $PORT_REALITY | UUID: $UUID" $ENABLE_ANYTLS && echo " AnyTLS 端口: $PORT_ANYTLS | 用户: $ANYTLS_USER | 密码: $ANYTLS_PSK" echo " 服务器: $PUB_IP" echo " Reality server_name(SNI): ${REALITY_SNI:-addons.mozilla.org}" echo "" info "📂 文件位置:" echo " 配置: $CONFIG_PATH" ($ENABLE_HY2 || $ENABLE_TUIC) && echo " 证书: /etc/sing-box/certs/" echo " 服务: $SERVICE_PATH" echo "" info "📜 客户端链接:" generate_uris | while IFS= read -r line; do echo " $line" done echo "" info "🔧 管理命令:" if [ "$OS" = "alpine" ]; then echo " 启动: rc-service sing-box start" echo " 停止: rc-service sing-box stop" echo " 重启: rc-service sing-box restart" echo " 状态: rc-service sing-box status" echo " 日志: tail -f /var/log/sing-box.log" else echo " 启动: systemctl start sing-box" echo " 停止: systemctl stop sing-box" echo " 重启: systemctl restart sing-box" echo " 状态: systemctl status sing-box" echo " 日志: journalctl -u sing-box -f" fi echo "" echo "==========================================" # ----------------------- # 创建 sb 管理脚本 SB_PATH="/usr/local/bin/sb" info "正在创建 sb 管理面板: $SB_PATH" cat > "$SB_PATH" <<'SB_SCRIPT' #!/usr/bin/env bash set -euo pipefail info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } err() { echo -e "\033[1;31m[ERR]\033[0m $*" >&2; } CONFIG_PATH="/etc/sing-box/config.json" CACHE_FILE="/etc/sing-box/.config_cache" SERVICE_NAME="sing-box" # 检测系统 detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release ID="${ID:-}" ID_LIKE="${ID_LIKE:-}" else ID="" ID_LIKE="" fi if echo "$ID $ID_LIKE" | grep -qi "alpine"; then OS="alpine" elif echo "$ID $ID_LIKE" | grep -Ei "debian|ubuntu" >/dev/null; then OS="debian" elif echo "$ID $ID_LIKE" | grep -Ei "centos|rhel|fedora" >/dev/null; then OS="redhat" else OS="unknown" fi } detect_os # 服务控制 service_start() { [ "$OS" = "alpine" ] && rc-service "$SERVICE_NAME" start || systemctl start "$SERVICE_NAME" } service_stop() { [ "$OS" = "alpine" ] && rc-service "$SERVICE_NAME" stop || systemctl stop "$SERVICE_NAME" } service_restart() { [ "$OS" = "alpine" ] && rc-service "$SERVICE_NAME" restart || systemctl restart "$SERVICE_NAME" } service_status() { [ "$OS" = "alpine" ] && rc-service "$SERVICE_NAME" status || systemctl status "$SERVICE_NAME" --no-pager } # 生成随机值 rand_port() { shuf -i 10000-60000 -n 1 2>/dev/null || echo $((RANDOM % 50001 + 10000)); } rand_pass() { openssl rand -base64 16 | tr -d '\n\r' || head -c 16 /dev/urandom | base64 | tr -d '\n\r'; } rand_uuid() { cat /proc/sys/kernel/random/uuid 2>/dev/null || openssl rand -hex 16 | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-\9\10-\11\12\13\14\15\16/'; } # URL 编码 url_encode() { printf "%s" "$1" | sed -e 's/%/%25/g' -e 's/:/%3A/g' -e 's/+/%2B/g' -e 's/\//%2F/g' -e 's/=/%3D/g' } # 读取配置 read_config() { if [ ! -f "$CONFIG_PATH" ]; then err "未找到配置文件: $CONFIG_PATH" return 1 fi # 优先加载 .protocols 文件(确认协议标记) PROTOCOL_FILE="/etc/sing-box/.protocols" if [ -f "$PROTOCOL_FILE" ]; then . "$PROTOCOL_FILE" fi # 加载缓存文件(包含端口密码等详细配置) if [ -f "$CACHE_FILE" ]; then . "$CACHE_FILE" fi # 确保有默认值 REALITY_SNI="${REALITY_SNI:-addons.mozilla.org}" ENABLE_ANYTLS="${ENABLE_ANYTLS:-false}" CUSTOM_IP="${CUSTOM_IP:-}" # 读取各协议配置 if [ "${ENABLE_SS:-false}" = "true" ]; then SS_PORT=$(jq -r '.inbounds[] | select(.type=="shadowsocks") | .listen_port // empty' "$CONFIG_PATH" | head -n1) SS_PSK=$(jq -r '.inbounds[] | select(.type=="shadowsocks") | .password // empty' "$CONFIG_PATH" | head -n1) SS_METHOD=$(jq -r '.inbounds[] | select(.type=="shadowsocks") | .method // empty' "$CONFIG_PATH" | head -n1) fi if [ "${ENABLE_HY2:-false}" = "true" ]; then HY2_PORT=$(jq -r '.inbounds[] | select(.type=="hysteria2") | .listen_port // empty' "$CONFIG_PATH" | head -n1) HY2_PSK=$(jq -r '.inbounds[] | select(.type=="hysteria2") | .users[0].password // empty' "$CONFIG_PATH" | head -n1) fi if [ "${ENABLE_TUIC:-false}" = "true" ]; then TUIC_PORT=$(jq -r '.inbounds[] | select(.type=="tuic") | .listen_port // empty' "$CONFIG_PATH" | head -n1) TUIC_UUID=$(jq -r '.inbounds[] | select(.type=="tuic") | .users[0].uuid // empty' "$CONFIG_PATH" | head -n1) TUIC_PSK=$(jq -r '.inbounds[] | select(.type=="tuic") | .users[0].password // empty' "$CONFIG_PATH" | head -n1) fi # Reality 公共参数(Reality / AnyTLS 共用) if [ "${ENABLE_REALITY:-false}" = "true" ] || [ "${ENABLE_ANYTLS:-false}" = "true" ]; then REALITY_SID=$(jq -r ' .inbounds[] | select(.tls.reality.enabled == true) | .tls.reality.short_id[0] // empty ' "$CONFIG_PATH" | head -n1) [ -f /etc/sing-box/.reality_pub ] && REALITY_PUB=$(cat /etc/sing-box/.reality_pub) fi # VLESS Reality 专属参数 if [ "${ENABLE_REALITY:-false}" = "true" ]; then REALITY_PORT=$(jq -r '.inbounds[] | select(.type=="vless") | .listen_port // empty' "$CONFIG_PATH" | head -n1) REALITY_UUID=$(jq -r '.inbounds[] | select(.type=="vless") | .users[0].uuid // empty' "$CONFIG_PATH" | head -n1) REALITY_PK=$(jq -r '.inbounds[] | select(.type=="vless") | .tls.reality.private_key // empty' "$CONFIG_PATH" | head -n1) fi if [ "${ENABLE_ANYTLS:-false}" = "true" ]; then ANYTLS_PORT=$(jq -r '.inbounds[] | select(.type=="anytls") | .listen_port // empty' "$CONFIG_PATH" | head -n1) ANYTLS_USER=$(jq -r '.inbounds[] | select(.type=="anytls") | .users[0].name // empty' "$CONFIG_PATH" | head -n1) ANYTLS_PSK=$(jq -r '.inbounds[] | select(.type=="anytls") | .users[0].password // empty' "$CONFIG_PATH" | head -n1) fi } # 获取公网IP(原始方法) get_public_ip() { local ip="" for url in "https://api.ipify.org" "https://ipinfo.io/ip" "https://ifconfig.me"; do ip=$(curl -s --max-time 5 "$url" 2>/dev/null | tr -d '[:space:]') [ -n "$ip" ] && echo "$ip" && return 0 done echo "YOUR_SERVER_IP" } # 生成并保存URI generate_uris() { read_config || return 1 # 优先使用用户自定义入口 IP if [ -n "${CUSTOM_IP:-}" ]; then PUBLIC_IP="$CUSTOM_IP" else PUBLIC_IP=$(get_public_ip) fi node_suffix=$(cat /root/node_names.txt 2>/dev/null || echo "") URI_FILE="/etc/sing-box/uris.txt" > "$URI_FILE" if [ "${ENABLE_SS:-false}" = "true" ]; then ss_userinfo="${SS_METHOD}:${SS_PSK}" ss_encoded=$(url_encode "$ss_userinfo") ss_b64=$(printf "%s" "$ss_userinfo" | base64 -w0 2>/dev/null || printf "%s" "$ss_userinfo" | base64 | tr -d '\n') echo "=== Shadowsocks (SS) ===" >> "$URI_FILE" echo "ss://${ss_encoded}@${PUBLIC_IP}:${SS_PORT}#ss${node_suffix}" >> "$URI_FILE" echo "ss://${ss_b64}@${PUBLIC_IP}:${SS_PORT}#ss${node_suffix}" >> "$URI_FILE" echo "" >> "$URI_FILE" fi if [ "${ENABLE_HY2:-false}" = "true" ]; then hy2_encoded=$(url_encode "$HY2_PSK") echo "=== Hysteria2 (HY2) ===" >> "$URI_FILE" echo "hy2://${hy2_encoded}@${PUBLIC_IP}:${HY2_PORT}/?sni=www.bing.com&alpn=h3&insecure=1#hy2${node_suffix}" >> "$URI_FILE" echo "" >> "$URI_FILE" fi if [ "${ENABLE_TUIC:-false}" = "true" ]; then tuic_encoded=$(url_encode "$TUIC_PSK") echo "=== TUIC ===" >> "$URI_FILE" echo "tuic://${TUIC_UUID}:${tuic_encoded}@${PUBLIC_IP}:${TUIC_PORT}/?congestion_control=bbr&alpn=h3&sni=www.bing.com&insecure=1#tuic${node_suffix}" >> "$URI_FILE" echo "" >> "$URI_FILE" fi if [ "${ENABLE_REALITY:-false}" = "true" ]; then REALITY_SNI="${REALITY_SNI:-addons.mozilla.org}" echo "=== VLESS Reality ===" >> "$URI_FILE" echo "vless://${REALITY_UUID}@${PUBLIC_IP}:${REALITY_PORT}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${REALITY_SNI}&fp=chrome&pbk=${REALITY_PUB}&sid=${REALITY_SID}#reality${node_suffix}" >> "$URI_FILE" echo "" >> "$URI_FILE" fi if [ "${ENABLE_ANYTLS:-false}" = "true" ]; then anytls_user_encoded=$(url_encode "$ANYTLS_USER") anytls_pass_encoded=$(url_encode "$ANYTLS_PSK") echo "=== AnyTLS Reality ===" >> "$URI_FILE" echo "anytls://${anytls_pass_encoded}@${PUBLIC_IP}:${ANYTLS_PORT}/?security=reality&sni=${REALITY_SNI}&fp=chrome&pbk=${REALITY_PUB}&sid=${REALITY_SID}#anytls${node_suffix}" >> "$URI_FILE" echo "" >> "$URI_FILE" fi info "URI 已保存到: $URI_FILE" } # 查看URI action_view_uri() { info "正在生成并显示 URI..." generate_uris || { err "生成 URI 失败"; return 1; } echo "" cat /etc/sing-box/uris.txt } # 查看配置文件路径 action_view_config() { echo "$CONFIG_PATH" } # 编辑配置 action_edit_config() { if [ ! -f "$CONFIG_PATH" ]; then err "配置文件不存在: $CONFIG_PATH" return 1 fi ${EDITOR:-nano} "$CONFIG_PATH" 2>/dev/null || ${EDITOR:-vi} "$CONFIG_PATH" if command -v sing-box >/dev/null 2>&1; then if sing-box check -c "$CONFIG_PATH" >/dev/null 2>&1; then info "配置校验通过,已重启服务" service_restart || warn "重启失败" generate_uris || true else warn "配置校验失败,服务未重启" fi fi } # 重置SS端口 action_reset_ss() { read_config || return 1 if [ "${ENABLE_SS:-false}" != "true" ]; then err "SS 协议未启用" return 1 fi read -p "输入新的 SS 端口(回车保持 $SS_PORT): " new_port new_port="${new_port:-$SS_PORT}" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" jq --argjson port "$new_port" ' .inbounds |= map(if .type=="shadowsocks" then .listen_port = $port else . end) ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" info "已启动服务并更新 SS 端口: $new_port" service_start || warn "启动服务失败" sleep 1 generate_uris || warn "生成 URI 失败" } # 重置HY2端口 action_reset_hy2() { read_config || return 1 if [ "${ENABLE_HY2:-false}" != "true" ]; then err "HY2 协议未启用" return 1 fi read -p "输入新的 HY2 端口(回车保持 $HY2_PORT): " new_port new_port="${new_port:-$HY2_PORT}" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" jq --argjson port "$new_port" ' .inbounds |= map(if .type=="hysteria2" then .listen_port = $port else . end) ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" info "已启动服务并更新 HY2 端口: $new_port" service_start || warn "启动服务失败" sleep 1 generate_uris || warn "生成 URI 失败" } # 重置TUIC端口 action_reset_tuic() { read_config || return 1 if [ "${ENABLE_TUIC:-false}" != "true" ]; then err "TUIC 协议未启用" return 1 fi read -p "输入新的 TUIC 端口(回车保持 $TUIC_PORT): " new_port new_port="${new_port:-$TUIC_PORT}" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" jq --argjson port "$new_port" ' .inbounds |= map(if .type=="tuic" then .listen_port = $port else . end) ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" info "已启动服务并更新 TUIC 端口: $new_port" service_start || warn "启动服务失败" sleep 1 generate_uris || warn "生成 URI 失败" } # 重置Vless Reality端口 action_reset_reality() { read_config || return 1 if [ "${ENABLE_REALITY:-false}" != "true" ]; then err "Vless Reality 协议未启用" return 1 fi read -p "输入新的 Vless Reality 端口(回车保持 $REALITY_PORT): " new_port new_port="${new_port:-$REALITY_PORT}" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" jq --argjson port "$new_port" ' .inbounds |= map(if .type=="vless" then .listen_port = $port else . end) ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" info "已启动服务并更新 Vless Reality 端口: $new_port" service_start || warn "启动服务失败" sleep 1 generate_uris || warn "生成 URI 失败" } # 重置AnyTLS Reality端口 action_reset_anytls() { read_config || return 1 if [ "${ENABLE_ANYTLS:-false}" != "true" ]; then err "AnyTLS Reality 协议未启用" return 1 fi read -p "输入新的 AnyTLS Reality 端口(回车保持 $ANYTLS_PORT): " new_port new_port="${new_port:-$ANYTLS_PORT}" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" jq --argjson port "$new_port" ' .inbounds |= map(if .type=="anytls" then .listen_port = $port else . end) ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" info "已启动服务并更新 AnyTLS Reality 端口: $new_port" service_start || warn "启动服务失败" sleep 1 generate_uris || warn "生成 URI 失败" } # 更新sing-box action_update() { info "开始更新 sing-box..." if [ "$OS" = "alpine" ]; then apk update && apk upgrade sing-box || bash <(curl -fsSL https://sing-box.app/install.sh) else bash <(curl -fsSL https://sing-box.app/install.sh) fi info "更新完成,已重启服务..." if command -v sing-box >/dev/null 2>&1; then NEW_VER=$(sing-box version 2>/dev/null | head -n1) info "当前版本: $NEW_VER" service_restart || warn "重启失败" fi } # 卸载 action_uninstall() { read -p "确认卸载 sing-box?(y/N): " confirm [[ ! "$confirm" =~ ^[Yy]$ ]] && info "已取消" && return 0 info "正在卸载..." service_stop || true if [ "$OS" = "alpine" ]; then rc-update del sing-box default 2>/dev/null || true rm -f /etc/init.d/sing-box apk del sing-box 2>/dev/null || true else systemctl stop sing-box 2>/dev/null || true systemctl disable sing-box 2>/dev/null || true rm -f /etc/systemd/system/sing-box.service systemctl daemon-reload 2>/dev/null || true apt purge -y sing-box >/dev/null 2>&1 || true fi rm -rf /etc/sing-box /var/log/sing-box* /usr/local/bin/sb /usr/bin/sing-box /root/node_names.txt 2>/dev/null || true info "卸载完成" } # 生成线路机脚本 action_generate_relay() { read_config || return 1 # 检查是否启用了SS if [ "${ENABLE_SS:-false}" != "true" ]; then warn "未检测到 SS 协议,需要先部署 SS 作为入站" read -p "是否现在部署 SS 协议?(y/N): " deploy_ss if [[ "$deploy_ss" =~ ^[Yy]$ ]]; then info "开始部署 SS 协议..." # 让用户选择端口 read -p "请输入 SS 端口(留空则随机 10000-60000): " USER_SS_PORT SS_PORT="${USER_SS_PORT:-$(rand_port)}" SS_PSK=$(rand_pass) SS_METHOD="aes-128-gcm" info "SS 端口: $SS_PORT | 密码已自动生成" info "正在停止服务..." service_stop || warn "停止服务失败" cp "$CONFIG_PATH" "${CONFIG_PATH}.bak" # 添加 SS inbound jq --argjson port "$SS_PORT" --arg psk "$SS_PSK" ' .inbounds += [{ "type": "shadowsocks", "listen": "::", "listen_port": $port, "method": "aes-128-gcm", "password": $psk, "tag": "ss-in" }] ' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp" && mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH" # 更新缓存和协议标记 sed -i 's/ENABLE_SS=false/ENABLE_SS=true/' "$CACHE_FILE" 2>/dev/null || echo "ENABLE_SS=true" >> "$CACHE_FILE" echo "SS_PORT=$SS_PORT" >> "$CACHE_FILE" echo "SS_PSK=$SS_PSK" >> "$CACHE_FILE" echo "SS_METHOD=$SS_METHOD" >> "$CACHE_FILE" # 同步更新协议标记文件 PROTOCOL_FILE="/etc/sing-box/.protocols" if [ -f "$PROTOCOL_FILE" ]; then sed -i 's/ENABLE_SS=false/ENABLE_SS=true/' "$PROTOCOL_FILE" else echo "ENABLE_SS=true" >> "$PROTOCOL_FILE" fi # 更新当前会话变量 ENABLE_SS=true info "SS 已部署 - 端口: $SS_PORT" service_start || warn "启动服务失败" sleep 1 # 重新读取配置 read_config else err "取消生成线路机脚本" return 1 fi fi # 线路机模板使用 CUSTOM_IP(若设置)或当前公共 IP if [ -n "${CUSTOM_IP:-}" ]; then INBOUND_IP="${CUSTOM_IP}" else INBOUND_IP="$(get_public_ip)" fi PUBLIC_IP="$INBOUND_IP" RELAY_SCRIPT="/tmp/relay-install.sh" info "正在生成线路机脚本: $RELAY_SCRIPT" cat > "$RELAY_SCRIPT" <<'RELAY_EOF' #!/usr/bin/env bash set -euo pipefail info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } err() { echo -e "\033[1;31m[ERR]\033[0m $*" >&2; } [ "$(id -u)" != "0" ] && err "必须以 root 运行" && exit 1 detect_os(){ . /etc/os-release 2>/dev/null || true case "${ID:-}" in alpine) OS=alpine ;; debian|ubuntu) OS=debian ;; centos|rhel|fedora) OS=redhat ;; *) OS=unknown ;; esac } detect_os info "安装依赖..." case "$OS" in alpine) apk update; apk add --no-cache curl jq bash openssl ca-certificates ;; debian) apt-get update -y; apt-get install -y curl jq bash openssl ca-certificates ;; redhat) yum install -y curl jq bash openssl ca-certificates ;; esac info "安装 sing-box..." case "$OS" in alpine) apk add --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community sing-box ;; *) bash <(curl -fsSL https://sing-box.app/install.sh) ;; esac UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "00000000-0000-0000-0000-000000000000") info "生成 Reality 密钥对" REALITY_KEYS=$(sing-box generate reality-keypair 2>/dev/null || echo "") REALITY_PK=$(echo "$REALITY_KEYS" | grep "PrivateKey" | awk '{print $NF}' | tr -d '\r' || echo "") REALITY_PUB=$(echo "$REALITY_KEYS" | grep "PublicKey" | awk '{print $NF}' | tr -d '\r' || echo "") REALITY_SID=$(sing-box generate rand 8 --hex 2>/dev/null || echo "0123456789abcdef") read -p "请输入线路机监听端口(留空随机 20000-65000): " USER_PORT LISTEN_PORT="${USER_PORT:-$(shuf -i 20000-65000 -n 1 2>/dev/null || echo 20443)}" mkdir -p /etc/sing-box cat > /etc/sing-box/config.json < /etc/init.d/sing-box <<'SVC' #!/sbin/openrc-run name="sing-box" command="/usr/bin/sing-box" command_args="run -c /etc/sing-box/config.json" command_background="yes" pidfile="/run/sing-box.pid" supervisor=supervise-daemon supervise_daemon_args="--respawn-max 0 --respawn-delay 5" depend() { need net; } SVC chmod +x /etc/init.d/sing-box rc-update add sing-box default rc-service sing-box restart else cat > /etc/systemd/system/sing-box.service <<'SYSTEMD' [Unit] Description=Sing-box Relay After=network.target [Service] ExecStart=/usr/bin/sing-box run -c /etc/sing-box/config.json Restart=on-failure RestartSec=10s [Install] WantedBy=multi-user.target SYSTEMD systemctl daemon-reload systemctl enable sing-box systemctl restart sing-box fi PUB_IP=$(curl -s https://api.ipify.org 2>/dev/null || echo "YOUR_RELAY_IP") # 生成并保存链接 RELAY_URI="vless://$UUID@$PUB_IP:$LISTEN_PORT?encryption=none&flow=xtls-rprx-vision&security=reality&sni=__REALITY_SNI__&fp=chrome&pbk=$REALITY_PUB&sid=$REALITY_SID#relay" mkdir -p /etc/sing-box echo "$RELAY_URI" > /etc/sing-box/relay_uri.txt echo "" info "✅ 安装完成" echo "=============== 中转节点 Reality 链接 ===============" echo "$RELAY_URI" echo "====================================================" echo "" info "💡 链接已保存到: /etc/sing-box/relay_uri.txt" info "💡 查看链接命令: cat /etc/sing-box/relay_uri.txt" RELAY_EOF # 替换占位符(INBOUND_IP/PORT/METHOD/PASSWORD 同时替换 REALITY_SNI) sed -i "s|__INBOUND_IP__|$INBOUND_IP|g" "$RELAY_SCRIPT" sed -i "s|__INBOUND_PORT__|$SS_PORT|g" "$RELAY_SCRIPT" sed -i "s|__INBOUND_METHOD__|$SS_METHOD|g" "$RELAY_SCRIPT" sed -i "s|__INBOUND_PASSWORD__|$SS_PSK|g" "$RELAY_SCRIPT" sed -i "s|__REALITY_SNI__|${REALITY_SNI:-addons.mozilla.org}|g" "$RELAY_SCRIPT" chmod +x "$RELAY_SCRIPT" info "✅ 线路机脚本已生成: $RELAY_SCRIPT" echo "" info "请复制以下内容到线路机执行:" echo "----------------------------------------" cat "$RELAY_SCRIPT" echo "----------------------------------------" echo "" info "在线路机执行命令示例:" echo " nano /tmp/relay-install.sh 保存后执行" echo " chmod +x /tmp/relay-install.sh && bash /tmp/relay-install.sh" echo "" info "复制执行完成后,即可在线路机完成 sing-box 中转节点部署。" } # 动态生成菜单 show_menu() { read_config 2>/dev/null || true cat <<'MENU' ========================== Sing-box 管理面板 (快速指令sb) ========================== 1) 查看协议链接 2) 查看配置文件路径 3) 编辑配置文件 MENU # 构建协议重置选项映射 declare -g -A MENU_MAP local option=4 if [ "${ENABLE_SS:-false}" = "true" ]; then echo "$option) 重置 SS 端口" MENU_MAP[$option]="reset_ss" option=$((option + 1)) fi if [ "${ENABLE_HY2:-false}" = "true" ]; then echo "$option) 重置 HY2 端口" MENU_MAP[$option]="reset_hy2" option=$((option + 1)) fi if [ "${ENABLE_TUIC:-false}" = "true" ]; then echo "$option) 重置 TUIC 端口" MENU_MAP[$option]="reset_tuic" option=$((option + 1)) fi if [ "${ENABLE_REALITY:-false}" = "true" ]; then echo "$option) 重置 Vless Reality 端口" MENU_MAP[$option]="reset_reality" option=$((option + 1)) fi if [ "${ENABLE_ANYTLS:-false}" = "true" ]; then echo "$option) 重置 AnyTLS Reality 端口" MENU_MAP[$option]="reset_anytls" option=$((option + 1)) fi # 固定功能选项 MENU_MAP[$option]="start" echo "$option) 启动服务" option=$((option + 1)) MENU_MAP[$option]="stop" echo "$((option))) 停止服务" option=$((option + 1)) MENU_MAP[$option]="restart" echo "$((option))) 重启服务" option=$((option + 1)) MENU_MAP[$option]="status" echo "$((option))) 查看状态" option=$((option + 1)) MENU_MAP[$option]="update" echo "$((option))) 更新 sing-box" option=$((option + 1)) MENU_MAP[$option]="relay" echo "$((option))) 生成线路机脚本(出口为本机ss协议)" option=$((option + 1)) MENU_MAP[$option]="uninstall" echo "$((option))) 卸载 sing-box" cat <