#!/usr/bin/env bash # 当前脚本版本号 VERSION='v1.1.10 (2024.03.26)' # 各变量默认值 GH_PROXY='' TEMP_DIR='/tmp/sing-box' WORK_DIR='/etc/sing-box' START_PORT_DEFAULT='8881' MIN_PORT=100 MAX_PORT=65520 TLS_SERVER_DEFAULT=addons.mozilla.org PROTOCOL_LIST=("XTLS + reality" "hysteria2" "tuic" "ShadowTLS" "shadowsocks" "trojan" "vmess + ws" "vless + ws + tls" "H2 + reality" "gRPC + reality") NODE_TAG=("xtls-reality" "hysteria2" "tuic" "ShadowTLS" "shadowsocks" "trojan" "vmess-ws" "vless-ws-tls" "h2-reality" "grpc-reality") CONSECUTIVE_PORTS=${#PROTOCOL_LIST[@]} CDN_DOMAIN=("cn.azhz.eu.org" "www.who.int" "skk.moe" "time.cloudflare.com" "csgo.com") SUBSCRIBE_TEMPLATE="https://raw.githubusercontent.com/fscarmen/client_template/main" trap "rm -rf $TEMP_DIR >/dev/null 2>&1 ; echo -e '\n' ;exit 1" INT QUIT TERM EXIT mkdir -p $TEMP_DIR E[0]="Language:\n 1. English (default) \n 2. 简体中文" C[0]="${E[0]}" E[1]="Thanks to UUb for the official change of the compilation, dependencies jq, qrencode from apt installation to download the binary file, reduce the installation time of about 15 seconds, the implementation of the project's positioning of lightweight, as far as possible to install the least system dependencies." C[1]="感谢 UUb 兄弟的官改编译,依赖 jq, qrencode 从 apt 安装改为下载二进制文件,缩减安装时间约15秒,贯彻项目轻量化的定位,尽最大可能安装最少的系统依赖" E[2]="Downloading Sing-box. Please wait a seconds ..." C[2]="下载 Sing-box 中,请稍等 ..." E[3]="Input errors up to 5 times.The script is aborted." C[3]="输入错误达5次,脚本退出" E[4]="UUID should be 36 characters, please re-enter \(\${UUID_ERROR_TIME} times remaining\):" C[4]="UUID 应为36位字符,请重新输入 \(剩余\${UUID_ERROR_TIME}次\):" E[5]="The script supports Debian, Ubuntu, CentOS, Alpine, Fedora or Arch systems only. Feedback: [https://github.com/fscarmen/sing-box/issues]" C[5]="本脚本只支持 Debian、Ubuntu、CentOS、Alpine、Fedora 或 Arch 系统,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[6]="Curren operating system is \$SYS.\\\n The system lower than \$SYSTEM \${MAJOR[int]} is not supported. Feedback: [https://github.com/fscarmen/sing-box/issues]" C[6]="当前操作是 \$SYS\\\n 不支持 \$SYSTEM \${MAJOR[int]} 以下系统,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[7]="Install dependence-list:" C[7]="安装依赖列表:" E[8]="All dependencies already exist and do not need to be installed additionally." C[8]="所有依赖已存在,不需要额外安装" E[9]="To upgrade, press [y]. No upgrade by default:" C[9]="升级请按 [y],默认不升级:" E[10]="\(4/6\) Please enter VPS IP \(Default is: \${SERVER_IP_DEFAULT}\):" C[10]="\(4/6\) 请输入 VPS IP \(默认为: \${SERVER_IP_DEFAULT}\):" E[11]="\(2/6\) Please enter the starting port number. Must be \${MIN_PORT} - \${MAX_PORT}, consecutive \${NUM} free ports are required \(Default is: \${START_PORT_DEFAULT}\):" C[11]="\(2/6\) 请输入开始的端口号,必须是 \${MIN_PORT} - \${MAX_PORT},需要连续\${NUM}个空闲的端口 \(默认为: \${START_PORT_DEFAULT}\):" E[12]="\(5/6\) Please enter UUID \(Default is \${UUID_DEFAULT}\):" C[12]="\(5/6\) 请输入 UUID \(默认为 \${UUID_DEFAULT}\):" E[13]="\(6/6\) Please enter the node name. \(Default is \${NODE_NAME_DEFAULT}\):" C[13]="\(6/6\) 请输入节点名称 \(默认为 \${NODE_NAME_DEFAULT}\):" E[14]="Node name only allow uppercase and lowercase letters and numeric characters, please re-enter \(\${a} times remaining\):" C[14]="节点名称只允许英文大小写及数字字符,请重新输入 \(剩余\${a}次\):" E[15]="Sing-box script has not been installed yet." C[15]="Sing-box 脚本还没有安装" E[16]="Sing-box is completely uninstalled." C[16]="Sing-box 已彻底卸载" E[17]="Version" C[17]="脚本版本" E[18]="New features" C[18]="功能新增" E[19]="System infomation" C[19]="系统信息" E[20]="Operating System" C[20]="当前操作系统" E[21]="Kernel" C[21]="内核" E[22]="Architecture" C[22]="处理器架构" E[23]="Virtualization" C[23]="虚拟化" E[24]="Choose:" C[24]="请选择:" E[25]="Curren architecture \$(uname -m) is not supported. Feedback: [https://github.com/fscarmen/sing-box/issues]" C[25]="当前架构 \$(uname -m) 暂不支持,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[26]="Not install" C[26]="未安装" E[27]="close" C[27]="关闭" E[28]="open" C[28]="开启" E[29]="View links (sb -n)" C[29]="查看节点信息 (sb -n)" E[30]="Change listen ports (sb -p)" C[30]="更换监听端口 (sb -p)" E[31]="Sync Sing-box to the latest version (sb -v)" C[31]="同步 Sing-box 至最新版本 (sb -v)" E[32]="Upgrade kernel, turn on BBR, change Linux system (sb -b)" C[32]="升级内核、安装BBR、DD脚本 (sb -b)" E[33]="Uninstall (sb -u)" C[33]="卸载 (sb -u)" E[34]="Install Sing-box multi-protocol script" C[34]="安装 Sing-box 协议全家桶脚本" E[35]="Exit" C[35]="退出" E[36]="Please enter the correct number" C[36]="请输入正确数字" E[37]="successful" C[37]="成功" E[38]="failed" C[38]="失败" E[39]="Sing-box is not installed." C[39]="Sing-box 未安装" E[40]="Sing-box local verion: \$LOCAL\\\t The newest verion: \$ONLINE" C[40]="Sing-box 本地版本: \$LOCAL\\\t 最新版本: \$ONLINE" E[41]="No upgrade required." C[41]="不需要升级" E[42]="Downloading the latest version Sing-box failed, script exits. Feedback:[https://github.com/fscarmen/sing-box/issues]" C[42]="下载最新版本 Sing-box 失败,脚本退出,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[43]="The script must be run as root, you can enter sudo -i and then download and run again. Feedback:[https://github.com/fscarmen/sing-box/issues]" C[43]="必须以root方式运行脚本,可以输入 sudo -i 后重新下载运行,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[44]="Ports are in used: \${IN_USED[*]}" C[44]="正在使用中的端口: \${IN_USED[*]}" E[45]="Ports used: \${NOW_START_PORT} - \$((NOW_START_PORT+NOW_CONSECUTIVE_PORTS-1))" C[45]="使用端口: \${NOW_START_PORT} - \$((NOW_START_PORT+NOW_CONSECUTIVE_PORTS-1))" E[46]="Warp / warp-go was detected to be running. Please enter the correct server IP:" C[46]="检测到 warp / warp-go 正在运行,请输入确认的服务器 IP:" E[47]="No server ip, script exits. Feedback:[https://github.com/fscarmen/sing-box/issues]" C[47]="没有 server ip,脚本退出,问题反馈:[https://github.com/fscarmen/sing-box/issues]" E[48]="ShadowTLS - Copy the above two Neko links and manually set up the chained proxies in order. Tutorial: https://github.com/fscarmen/sing-box/blob/main/README.md#sekobox-%E8%AE%BE%E7%BD%AE-shadowtls-%E6%96%B9%E6%B3%95" C[48]="ShadowTLS - 复制上面两条 Neko links 进去,并按顺序手动设置链式代理,详细教程: https://github.com/fscarmen/sing-box/blob/main/README.md#sekobox-%E8%AE%BE%E7%BD%AE-shadowtls-%E6%96%B9%E6%B3%95" E[49]="(1/6) Select more protocols to install (e.g. hgbd):\n a. all (default)" C[49]="(1/6) 多选需要安装协议(比如 hgbd):\n a. all (默认)" E[50]="Please enter the \$TYPE domain name:" C[50]="请输入 \$TYPE 域名:" E[51]="Please choose or custom a cdn, http support is required:" C[51]="请选择或输入 cdn,要求支持 http:" E[52]="Please set the ip \[\${WS_SERVER_IP_SHOW}] to domain \[\${TYPE_HOST_DOMAIN}], and set the origin rule to \[\${TYPE_PORT_WS}] in Cloudflare." C[52]="请在 Cloudflare 绑定 \[\${WS_SERVER_IP_SHOW}] 的域名为 \[\${TYPE_HOST_DOMAIN}], 并设置 origin rule 为 \[\${TYPE_PORT_WS}]" E[53]="Please select or enter the preferred domain, the default is \${CDN_DOMAIN[0]}:" C[53]="请选择或者填入优选域名,默认为 \${CDN_DOMAIN[0]}:" E[54]="The contents of the ShadowTLS configuration file need to be updated for the sing_box kernel." C[54]="ShadowTLS 配置文件内容,需要更新 sing_box 内核" E[55]="The script runs today: \$TODAY. Total: \$TOTAL" C[55]="脚本当天运行次数: \$TODAY,累计运行次数: \$TOTAL" E[56]="Process ID" C[56]="进程ID" E[57]="Runtime" C[57]="运行时长" E[58]="Memory Usage" C[58]="内存占用" E[59]="Install ArgoX scripts (argo + xray) [https://github.com/fscarmen/argox]" C[59]="安装 ArgoX 脚本 (argo + xray) [https://github.com/fscarmen/argox]" E[60]="The order of the selected protocols and ports is as follows:" C[60]="选择的协议及端口次序如下:" E[61]="(DNS your own domain in Cloudflare is required.)" C[61]="(必须在 Cloudflare 解析自有域名)" E[62]="Add / Remove protocols (sb -r)" C[62]="增加 / 删除协议 (sb -r)" E[63]="(1/3) Installed protocols." C[63]="(1/3) 已安装的协议" E[64]="Please select the protocols to be removed (multiple selections possible):" C[64]="请选择需要删除的协议(可以多选):" E[65]="(2/3) Uninstalled protocols." C[65]="(2/3) 未安装的协议" E[66]="Please select the protocols to be added (multiple choices possible):" C[66]="请选择需要增加的协议(可以多选):" E[67]="(3/3) Confirm all protocols for reloading." C[67]="(3/3) 确认重装的所有协议" E[68]="Press [n] if there is an error, other keys to continue:" C[68]="如有错误请按 [n],其他键继续:" E[69]="Install sba scripts (argo + sing-box) [https://github.com/fscarmen/sba]" C[69]="安装 sba 脚本 (argo + sing-box) [https://github.com/fscarmen/sba]" E[70]="Please set inSecure in tls to true." C[70]="请把 tls 里的 inSecure 设置为 true" E[71]="Create shortcut [ sb ] successfully." C[71]="创建快捷 [ sb ] 指令成功!" E[72]="Path to each client configuration file: $WORK_DIR/subscribe/\n The full template can be found at:\n https://t.me/ztvps/96\n https://github.com/chika0801/sing-box-examples/tree/main/Tun" C[72]="各客户端配置文件路径: $WORK_DIR/subscribe/\n 完整模板可参照:\n https://t.me/ztvps/96\n https://github.com/chika0801/sing-box-examples/tree/main/Tun" E[73]="There is no protocol left, if you are sure please re-run [ sb -u ] to uninstall all." C[73]="没有协议剩下,如确定请重新执行 [ sb -u ] 卸载所有" E[74]="Keep protocols" C[74]="保留协议" E[75]="Add protocols" C[75]="新增协议" E[76]="Install TCP brutal" C[76]="安装 TCP brutal" E[77]="With sing-box installed, the script exits." C[77]="已安装 sing-box ,脚本退出" E[78]="Parameter [ $ERROR_PARAMETER ] error, script exits." C[78]="[ $ERROR_PARAMETER ] 参数错误,脚本退出" E[79]="\(3/6\) Output subscription QR code and http service, need to install nginx\\\n If not, please enter [n]\\\n If you need, please enter the port number of nginx used for subscription service. Must be \${MIN_PORT} - \${MAX_PORT} \(Default is: \${PORT_NGINX_DEFAULT}\):" C[79]="\(3/6\) 输出订阅二维码和 http 服务,需要安装依赖 nginx\\\n 如不需要,请输入 [n]\\\n 如需要请输入用于订阅服务的 nginx 端口号,必须是 \${MIN_PORT} - \${MAX_PORT} \(默认为: \${PORT_NGINX_DEFAULT}\):" E[80]="subscribe" C[80]="订阅" E[81]="Adaptive Clash / V2rayN / NekoBox / ShadowRocket / SFI / SFA / SFM Clients" C[81]="自适应 Clash / V2rayN / NekoBox / ShadowRocket / SFI / SFA / SFM 客户端" E[82]="template" C[82]="模版" E[83]="To uninstall Nginx press [y], it is not uninstalled by default:" C[83]="如要卸载 Nginx 请按 [y],默认不卸载:" E[84]="Set SElinux: enforcing --> disabled" C[84]="设置 SElinux: enforcing --> disabled" # 自定义字体彩色,read 函数 warning() { echo -e "\033[31m\033[01m$*\033[0m"; } # 红色 error() { echo -e "\033[31m\033[01m$*\033[0m" && exit 1; } # 红色 info() { echo -e "\033[32m\033[01m$*\033[0m"; } # 绿色 hint() { echo -e "\033[33m\033[01m$*\033[0m"; } # 黄色 reading() { read -rp "$(info "$1")" "$2"; } text() { grep -q '\$' <<< "${E[$*]}" && eval echo "\$(eval echo "\${${L}[$*]}")" || eval echo "\${${L}[$*]}"; } # 自定义谷歌翻译函数 translate() { [ -n "$@" ] && EN="$@" ZH=$(wget --no-check-certificate -qO- --tries=1 --timeout=2 "https://translate.google.com/translate_a/t?client=any_client_id_works&sl=en&tl=zh&q=${EN//[[:space:]]/%20}" 2>/dev/null) [[ "$ZH" =~ ^\[\".+\"\]$ ]] && awk -F '"' '{print $2}' <<< "$ZH" } # 检测是否需要启用 Github CDN,如能直接连通,则不使用 check_cdn() { [ -n "$GH_PROXY" ] && wget --server-response --quiet --output-document=/dev/null --no-check-certificate --tries=2 --timeout=3 https://raw.githubusercontent.com/fscarmen/sing-box/main/README.md >/dev/null 2>&1 && unset GH_PROXY } # 检测是否解锁 chatGPT,以决定是否使用 warp 链式代理或者是 direct out check_chatgpt() { local CHECK_STACK=$1 local SUPPORT_COUNTRY=(AL DZ AD AO AG AR AM AU AT AZ BS BD BB BE BZ BJ BT BO BA BW BR BN BG BF CV CA CL CO KM CG CR CI HR CY CZ DK DJ DM DO EC SV EE FJ FI FR GA GM GE DE GH GR GD GT GN GW GY HT VA HN HU IS IN ID IQ IE IL IT JM JP JO KZ KE KI KW KG LV LB LS LR LI LT LU MG MW MY MV ML MT MH MR MU MX FM MD MC MN ME MA MZ MM NA NR NP NL NZ NI NE NG MK NO OM PK PW PS PA PG PY PE PH PL PT QA RO RW KN LC VC WS SM ST SN RS SC SL SG SK SI SB ZA KR ES LK SR SE CH TW TZ TH TL TG TO TT TN TR TV UG UA AE GB US UY VU ZM) [[ "${SUPPORT_COUNTRY[@]}" =~ $(wget --no-check-certificate -$CHECK_STACK -qO- --tries=3 --timeout=2 https://chat.openai.com/cdn-cgi/trace | awk -F '=' '/loc/{print $2}') ]] && echo 'unlock' || echo 'ban' } # 脚本当天及累计运行次数统计 statistics_of_run-times() { local COUNT=$(wget --no-check-certificate -qO- --tries=2 --timeout=2 "https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffscarmen%2Fsing-box%2Fmain%2Fsing-box.sh" 2>&1 | grep -m1 -oE "[0-9]+[ ]+/[ ]+[0-9]+") && TODAY=$(awk -F ' ' '{print $1}' <<< "$COUNT") && TOTAL=$(awk -F ' ' '{print $3}' <<< "$COUNT") } # 选择中英语言 select_language() { if [ -z "$L" ]; then case $(cat $WORK_DIR/language 2>&1) in E ) L=E ;; C ) L=C ;; * ) [ -z "$L" ] && L=E && hint "\n $(text 0) \n" && reading " $(text 24) " LANGUAGE [ "$LANGUAGE" = 2 ] && L=C ;; esac fi } # 字母与数字的 ASCII 码值转换 asc(){ if [[ "$1" = [a-z] ]]; then [ "$2" = '++' ] && printf "\\$(printf '%03o' "$[ $(printf "%d" "'$1'") + 1 ]")" || printf "%d" "'$1'" else [[ "$1" =~ ^[0-9]+$ ]] && printf "\\$(printf '%03o' "$1")" fi } input_cdn() { # 提供网上热心网友的anycast域名 if [[ -z "$CDN" && -n "$VMESS_HOST_DOMAIN$VLESS_HOST_DOMAIN" ]]; then echo "" for c in "${!CDN_DOMAIN[@]}"; do hint " $[c+1]. ${CDN_DOMAIN[c]} "; done reading "\n $(text 53) " CUSTOM_CDN case "$CUSTOM_CDN" in [1-${#CDN_DOMAIN[@]}] ) CDN="${CDN_DOMAIN[$((CUSTOM_CDN-1))]}" ;; ?????* ) CDN="$CUSTOM_CDN" ;; * ) CDN="${CDN_DOMAIN[0]}" esac fi } # | 选择输出模式 | 订阅 二维码输出 | nginx 输出 | 额外依赖 | # | mode 1 | X | X | - | # | mode 2 | √ | √ | nginx | enter_export_mode() { local NUM=$1 local PORT_ERROR_TIME=6 # 生成 1000 - 65535 随机默认端口数 local PORT_NGINX_DEFAULT=$(shuf -i ${MIN_PORT}-${MAX_PORT} -n 1) while true; do [ "$PORT_ERROR_TIME" -lt 6 ] && unset IN_USED PORT_NGINX (( PORT_ERROR_TIME-- )) || true if [ "$PORT_ERROR_TIME" = 0 ]; then error "\n $(text 3) \n" else [ -z "$PORT_NGINX" ] && reading "\n $(text 79) " PORT_NGINX fi PORT_NGINX=${PORT_NGINX:-"$PORT_NGINX_DEFAULT"} if [ "${PORT_NGINX,,}" = 'n' ]; then EXPORT_MODE=1 break elif [[ "$PORT_NGINX" =~ ^[1-9][0-9]{1,4}$ && "$PORT_NGINX" -ge "$MIN_PORT" && "$PORT_NGINX" -le "$MAX_PORT" ]]; then EXPORT_MODE=2 ss -nltup | grep -q ":$PORT_NGINX" && warning "\n $(text 44) \n" || break fi done } check_root() { [ "$(id -u)" != 0 ] && error "\n $(text 43) \n" } # 判断处理器架构 check_arch() { case $(uname -m) in aarch64|arm64 ) SING_BOX_ARCH=arm64; JQ_ARCH=arm64; QRENCODE_ARCH=arm64 ;; x86_64|amd64 ) [[ "$(awk -F ':' '/flags/{print $2; exit}' /proc/cpuinfo)" =~ avx2 ]] && SING_BOX_ARCH=amd64v3 || SING_BOX_ARCH=amd64; JQ_ARCH=amd64; QRENCODE_ARCH=amd64 ;; armv7l ) SING_BOX_ARCH=armv7; JQ_ARCH=armhf; QRENCODE_ARCH=arm ;; * ) error " $(text 25) " esac } # 查安装及运行状态;状态码: 26 未安装, 27 已安装未运行, 28 运行中 check_install() { [ -s $WORK_DIR/nginx.conf ] && IS_NGINX=is_nginx || IS_NGINX=no_nginx STATUS=$(text 26) && [ -s /etc/systemd/system/sing-box.service ] && STATUS=$(text 27) && [ "$(systemctl is-active sing-box)" = 'active' ] && STATUS=$(text 28) if [[ $STATUS = "$(text 26)" ]] && [ ! -s $WORK_DIR/sing-box ]; then { local VERSION_LATEST=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v-]' '/tag_name/{print $5}' | sort -r | sed -n '1p') local ONLINE=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v]' -v var="tag_name.*$VERSION_LATEST" '$0 ~ var {print $5; exit}') ONLINE=${ONLINE:-'1.9.0-rc.2'} wget --no-check-certificate --continue ${GH_PROXY}https://github.com/SagerNet/sing-box/releases/download/v$ONLINE/sing-box-$ONLINE-linux-$SING_BOX_ARCH.tar.gz -qO- | tar xz -C $TEMP_DIR sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box >/dev/null 2>&1 [ -s $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ] && mv $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box $TEMP_DIR wget --no-check-certificate --continue -qO $TEMP_DIR/jq ${GH_PROXY}https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-$JQ_ARCH >/dev/null 2>&1 && chmod +x $TEMP_DIR/jq >/dev/null 2>&1 wget --no-check-certificate --continue -qO $TEMP_DIR/qrencode https://github.com/fscarmen/client_template/raw/main/qrencode-go/qrencode-go-linux-$QRENCODE_ARCH >/dev/null 2>&1 && chmod +x $TEMP_DIR/qrencode >/dev/null 2>&1 }& fi } # 检测 sing-box 的状态 check_sing-box_stats(){ case "$STATUS" in "$(text 26)" ) error "\n Sing-box $(text 28) $(text 38) \n" ;; "$(text 27)" ) cmd_systemctl enable sing-box && info "\n Sing-box $(text 28) $(text 37) \n" || error "\n Sing-box $(text 28) $(text 38) \n" ;; "$(text 28)" ) info "\n Sing-box $(text 28) $(text 37) \n" esac } # 为了适配 alpine,定义 cmd_systemctl 的函数 cmd_systemctl() { local ENABLE_DISABLE=$1 local APP=$2 if [ "$ENABLE_DISABLE" = 'enable' ]; then if [ "$SYSTEM" = 'Alpine' ]; then systemctl start $APP cat > /etc/local.d/$APP.start << EOF #!/usr/bin/env bash systemctl start $APP EOF chmod +x /etc/local.d/$APP.start rc-update add local >/dev/null 2>&1 elif [ "$IS_CENTOS" = 'CentOS7' ]; then systemctl enable --now $APP [ "$IS_NGINX" = 'is_nginx' ] && $(type -p nginx) -c $WORK_DIR/nginx.conf else systemctl enable --now $APP fi elif [ "$ENABLE_DISABLE" = 'disable' ]; then if [ "$SYSTEM" = 'Alpine' ]; then systemctl stop $APP ss -nltp | grep $(awk '/listen/{print $2; exit}' $WORK_DIR/nginx.conf) | tr ',' '\n' | awk -F '=' '/pid/{print $2}' | sort -u | xargs kill -15 >/dev/null 2>&1 rm -f /etc/local.d/$APP.start elif [ "$IS_CENTOS" = 'CentOS7' ]; then systemctl disable --now $APP [ "$IS_NGINX" = 'is_nginx' ] && ss -nltp | grep $(awk '/listen/{print $2; exit}' $WORK_DIR/nginx.conf) | tr ',' '\n' | awk -F '=' '/pid/{print $2}' | sort -u | xargs kill -15 >/dev/null 2>&1 else systemctl disable --now $APP fi fi } check_system_info() { # 判断虚拟化 if [ $(type -p systemd-detect-virt) ]; then VIRT=$(systemd-detect-virt) elif [ $(type -p hostnamectl) ]; then VIRT=$(hostnamectl | awk '/Virtualization/{print $NF}') elif [ $(type -p virt-what) ]; then VIRT=$(virt-what) fi [ -s /etc/os-release ] && SYS="$(awk -F '"' 'tolower($0) ~ /pretty_name/{print $2}' /etc/os-release)" [[ -z "$SYS" && $(type -p hostnamectl) ]] && SYS="$(hostnamectl | awk -F ': ' 'tolower($0) ~ /operating system/{print $2}')" [[ -z "$SYS" && $(type -p lsb_release) ]] && SYS="$(lsb_release -sd)" [[ -z "$SYS" && -s /etc/lsb-release ]] && SYS="$(awk -F '"' 'tolower($0) ~ /distrib_description/{print $2}' /etc/lsb-release)" [[ -z "$SYS" && -s /etc/redhat-release ]] && SYS="$(cat /etc/redhat-release)" [[ -z "$SYS" && -s /etc/issue ]] && SYS="$(sed -E '/^$|^\\/d' /etc/issue | awk -F '\\' '{print $1}' | sed 's/[ ]*$//g')" REGEX=("debian" "ubuntu" "centos|red hat|kernel|alma|rocky" "arch linux" "alpine" "fedora") RELEASE=("Debian" "Ubuntu" "CentOS" "Arch" "Alpine" "Fedora") EXCLUDE=("") MAJOR=("9" "16" "7" "3" "" "37") PACKAGE_UPDATE=("apt -y update" "apt -y update" "yum -y update" "pacman -Sy" "apk update -f" "dnf -y update") PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install" "pacman -S --noconfirm" "apk add --no-cache" "dnf -y install") PACKAGE_UNINSTALL=("apt -y autoremove" "apt -y autoremove" "yum -y autoremove" "pacman -Rcnsu --noconfirm" "apk del -f" "dnf -y autoremove") for int in "${!REGEX[@]}"; do [[ "${SYS,,}" =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && break done # 针对各厂商的订制系统 if [ -z "$SYSTEM" ]; then [ $(type -p yum) ] && int=2 && SYSTEM='CentOS' || error " $(text 5) " fi # 先排除 EXCLUDE 里包括的特定系统,其他系统需要作大发行版本的比较 for ex in "${EXCLUDE[@]}"; do [[ ! "{$SYS,,}" =~ $ex ]]; done && [[ "$(echo "$SYS" | sed "s/[^0-9.]//g" | cut -d. -f1)" -lt "${MAJOR[int]}" ]] && error " $(text 6) " # 针对部分系统作特殊处理 [ "$SYSTEM" = 'CentOS' ] && IS_CENTOS="CentOS$(echo "$SYS" | sed "s/[^0-9.]//g" | cut -d. -f1)" } # 检测 IPv4 IPv6 信息 check_system_ip() { IP4=$(wget -4 -qO- --no-check-certificate --user-agent=Mozilla --tries=2 --timeout=1 http://ip-api.com/json/) && WAN4=$(expr "$IP4" : '.*query\":[ ]*\"\([^"]*\).*') && COUNTRY4=$(expr "$IP4" : '.*country\":[ ]*\"\([^"]*\).*') && ASNORG4=$(expr "$IP4" : '.*isp\":[ ]*\"\([^"]*\).*') && [[ "$L" = C && -n "$COUNTRY4" ]] && COUNTRY4=$(translate "$COUNTRY4") IP6=$(wget -6 -qO- --no-check-certificate --user-agent=Mozilla --tries=2 --timeout=1 https://api.ip.sb/geoip) && WAN6=$(expr "$IP6" : '.*ip\":[ ]*\"\([^"]*\).*') && COUNTRY6=$(expr "$IP6" : '.*country\":[ ]*\"\([^"]*\).*') && ASNORG6=$(expr "$IP6" : '.*isp\":[ ]*\"\([^"]*\).*') && [[ "$L" = C && -n "$COUNTRY6" ]] && COUNTRY6=$(translate "$COUNTRY6") } # 输入起始 port 函数 enter_start_port() { local NUM=$1 local PORT_ERROR_TIME=6 while true; do [ "$PORT_ERROR_TIME" -lt 6 ] && unset IN_USED START_PORT (( PORT_ERROR_TIME-- )) || true if [ "$PORT_ERROR_TIME" = 0 ]; then error "\n $(text 3) \n" else [ -z "$START_PORT" ] && reading "\n $(text 11) " START_PORT fi START_PORT=${START_PORT:-"$START_PORT_DEFAULT"} if [[ "$START_PORT" =~ ^[1-9][0-9]{2,4}$ && "$START_PORT" -ge "$MIN_PORT" && "$START_PORT" -le "$MAX_PORT" ]]; then for port in $(eval echo {$START_PORT..$[START_PORT+NUM-1]}); do ss -nltup | grep -q ":$port" && IN_USED+=("$port") done [ "${#IN_USED[*]}" -eq 0 ] && break || warning "\n $(text 44) \n" fi done } # 定义 Sing-box 变量 sing-box_variable() { if grep -qi 'cloudflare' <<< "$ASNORG4$ASNORG6"; then local a=6 until [ -n "$SERVER_IP" ]; do ((a--)) || true [ "$a" = 0 ] && error "\n $(text 3) \n" reading "\n $(text 46) " SERVER_IP done if [[ "$SERVER_IP" =~ : ]]; then WARP_ENDPOINT=2606:4700:d0::a29f:c101 DOMAIN_STRATEG=prefer_ipv6 else WARP_ENDPOINT=162.159.193.10 DOMAIN_STRATEG=prefer_ipv4 fi elif [ -n "$WAN4" ]; then SERVER_IP_DEFAULT=$WAN4 WARP_ENDPOINT=162.159.193.10 DOMAIN_STRATEG=prefer_ipv4 elif [ -n "$WAN6" ]; then SERVER_IP_DEFAULT=$WAN6 WARP_ENDPOINT=2606:4700:d0::a29f:c101 DOMAIN_STRATEG=prefer_ipv6 fi # 检测是否解锁 chatGPT CHAT_GPT_OUT_V4=warp-IPv4-out; CHAT_GPT_OUT_V6=warp-IPv6-out; [ "$(check_chatgpt ${DOMAIN_STRATEG: -1})" = 'unlock' ] && CHAT_GPT_OUT_V4=direct && CHAT_GPT_OUT_V6=direct # 选择安装的协议,由于选项 a 为全部协议,所以选项数不是从 a 开始,而是从 b 开始,处理输入:把大写全部变为小写,把不符合的选项去掉,把重复的选项合并 MAX_CHOOSE_PROTOCOLS=$(asc $[CONSECUTIVE_PORTS+96+1]) if [ -z "$CHOOSE_PROTOCOLS" ]; then hint "\n $(text 49) " for e in "${!PROTOCOL_LIST[@]}"; do [[ "$e" =~ '6'|'7' ]] && hint " $(asc $[e+98]). ${PROTOCOL_LIST[e]} $(text 61) " || hint " $(asc $[e+98]). ${PROTOCOL_LIST[e]} " done reading "\n $(text 24) " CHOOSE_PROTOCOLS fi # 对选择协议的输入处理逻辑:先把所有的大写转为小写,并把所有没有去选项剔除掉,最后按输入的次序排序。如果选项为 a(all) 和其他选项并存,将会忽略 a,如 abc 则会处理为 bc [[ ! "${CHOOSE_PROTOCOLS,,}" =~ [b-$MAX_CHOOSE_PROTOCOLS] ]] && INSTALL_PROTOCOLS=($(eval echo {b..$MAX_CHOOSE_PROTOCOLS})) || INSTALL_PROTOCOLS=($(grep -o . <<< "$CHOOSE_PROTOCOLS" | sed "/[^b-$MAX_CHOOSE_PROTOCOLS]/d" | awk '!seen[$0]++')) # 显示选择协议及其次序,输入开始端口号 if [ -z "$START_PORT" ]; then hint "\n $(text 60) " for w in "${!INSTALL_PROTOCOLS[@]}"; do [ "$w" -ge 9 ] && hint " $[w+1]. ${PROTOCOL_LIST[$(($(asc ${INSTALL_PROTOCOLS[w]}) - 98))]} " || hint " $[w+1] . ${PROTOCOL_LIST[$(($(asc ${INSTALL_PROTOCOLS[w]}) - 98))]} " done enter_start_port ${#INSTALL_PROTOCOLS[@]} fi # 输出模式选择,输入用于订阅的 Nginx 服务端口号, 后台根据选择安装依赖 enter_export_mode # 输入服务器 IP,默认为检测到的服务器 IP,如果全部为空,则提示并退出脚本 [ -z "$SERVER_IP" ] && reading "\n $(text 10) " SERVER_IP SERVER_IP=${SERVER_IP:-"$SERVER_IP_DEFAULT"} && WS_SERVER_IP_SHOW=$SERVER_IP [ -z "$SERVER_IP" ] && error " $(text 47) " # 如选择有 h. vmess + ws 或 i. vless + ws 时,先检测是否有支持的 http 端口可用,如有则要求输入域名和 cdn if [[ "${INSTALL_PROTOCOLS[@]}" =~ 'h' ]]; then local DOMAIN_ERROR_TIME=5 until [ -n "$VMESS_HOST_DOMAIN" ]; do (( DOMAIN_ERROR_TIME-- )) || true [ "$DOMAIN_ERROR_TIME" != 0 ] && TYPE=VMESS && reading "\n $(text 50) " VMESS_HOST_DOMAIN || error "\n $(text 3) \n" done fi if [[ "${INSTALL_PROTOCOLS[@]}" =~ 'i' ]]; then local DOMAIN_ERROR_TIME=5 until [ -n "$VLESS_HOST_DOMAIN" ]; do (( DOMAIN_ERROR_TIME-- )) || true [ "$DOMAIN_ERROR_TIME" != 0 ] && TYPE=VLESS && reading "\n $(text 50) " VLESS_HOST_DOMAIN || error "\n $(text 3) \n" done fi # 选择或者输入 cdn input_cdn # 输入 UUID ,错误超过 5 次将会退出 UUID_DEFAULT=$(cat /proc/sys/kernel/random/uuid) [ -z "$UUID_CONFIRM" ] && reading "\n $(text 12) " UUID_CONFIRM local UUID_ERROR_TIME=5 until [[ -z "$UUID_CONFIRM" || "${UUID_CONFIRM,,}" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]]; do (( UUID_ERROR_TIME-- )) || true [ "$UUID_ERROR_TIME" = 0 ] && error "\n $(text 3) \n" || reading "\n $(text 4) \n" UUID_CONFIRM done UUID_CONFIRM=${UUID_CONFIRM:-"$UUID_DEFAULT"} # 输入节点名,以系统的 hostname 作为默认 if [ $(type -p hostname) ]; then NODE_NAME_DEFAULT="$(hostname)" elif [ -s /etc/hostname ]; then NODE_NAME_DEFAULT="$(cat /etc/hostname)" else NODE_NAME_DEFAULT="Sing-Box" fi reading "\n $(text 13) " NODE_NAME_CONFIRM NODE_NAME_CONFIRM="${NODE_NAME_CONFIRM:-"$NODE_NAME_DEFAULT"}" } check_dependencies() { # 如果是 Alpine,先升级 wget ,安装 systemctl-py 版 if [ "$SYSTEM" = 'Alpine' ]; then local CHECK_WGET=$(wget 2>&1 | head -n 1) grep -qi 'busybox' <<< "$CHECK_WGET" && ${PACKAGE_INSTALL[int]} wget >/dev/null 2>&1 local DEPS_CHECK=("bash" "rc-update" "virt-what" "python3") local DEPS_INSTALL=("bash" "openrc" "virt-what" "python3") for g in "${!DEPS_CHECK[@]}"; do [ ! $(type -p ${DEPS_CHECK[g]}) ] && DEPS_ALPINE+=(${DEPS_INSTALL[g]}) done if [ "${#DEPS_ALPINE[@]}" -ge 1 ]; then info "\n $(text 7) $(sed "s/ /,&/g" <<< ${DEPS_ALPINE[@]}) \n" ${PACKAGE_UPDATE[int]} >/dev/null 2>&1 ${PACKAGE_INSTALL[int]} >/dev/null 2>&1 ${DEPS_ALPINE[@]} >/dev/null 2>&1 fi [ ! $(type -p systemctl) ] && wget --no-check-certificate --quiet https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl3.py -O /bin/systemctl && chmod a+x /bin/systemctl fi # 检测 Linux 系统的依赖,升级库并重新安装依赖 local DEPS_CHECK=("wget" "systemctl" "ss" "bash" "openssl") local DEPS_INSTALL=("wget" "systemctl" "iproute2" "bash" "openssl") for g in "${!DEPS_CHECK[@]}"; do [ ! $(type -p ${DEPS_CHECK[g]}) ] && DEPS+=(${DEPS_INSTALL[g]}) done if [ "${#DEPS[@]}" -ge 1 ]; then info "\n $(text 7) $(sed "s/ /,&/g" <<< ${DEPS[@]}) \n" [ "$SYSTEM" != 'CentOS' ] && ${PACKAGE_UPDATE[int]} >/dev/null 2>&1 ${PACKAGE_INSTALL[int]} ${DEPS[@]} >/dev/null 2>&1 else info "\n $(text 8) \n" fi } # 检查并安装 nginx check_nginx() { if [ ! $(type -p nginx) ]; then info "\n $(text 7) nginx \n" ${PACKAGE_INSTALL[int]} nginx >/dev/null 2>&1 # 如果新安装的 Nginx ,先停掉服务 systemctl disable --now nginx >/dev/null 2>&1 fi } # 生成100年的自签证书 ssl_certificate() { mkdir -p $WORK_DIR/cert openssl ecparam -genkey -name prime256v1 -out $WORK_DIR/cert/private.key && openssl req -new -x509 -days 36500 -key $WORK_DIR/cert/private.key -out $WORK_DIR/cert/cert.pem -subj "/CN=$(awk -F . '{print $(NF-1)"."$NF}' <<< "$TLS_SERVER_DEFAULT")" } # 处理防火墙规则 check_firewall_configuration() { if [[ -s /etc/selinux/config && $(type -p getenforce) && $(getenforce) = 'Enforcing' ]]; then hint "\n $(text 84) " setenforce 0 sed -i 's/^SELINUX=.*/# &/; /SELINUX=/a\SELINUX=disabled' /etc/selinux/config fi } # Nginx 配置文件 json_nginx() { cat > $WORK_DIR/nginx.conf << EOF user root; worker_processes auto; error_log /dev/null; pid /var/run/nginx.pid; events { worker_connections 1024; } http { map \$http_user_agent \$path1 { default /; # 默认路径 ~*v2rayN /v2rayn; # 匹配 V2rayN 客户端 ~*clash /clash; # 匹配 Clash 客户端 ~*Neko /neko; # 匹配 Neko 客户端 ~*ShadowRocket /shadowrocket; # 匹配 ShadowRocket 客户端 ~*SFM /sing-box-pc; # 匹配 Sing-box pc 客户端 ~*SFI|SFA /sing-box-phone; # 匹配 Sing-box phone 客户端 # ~*Chrome|Firefox|Mozilla /; # 添加更多的分流规则 } map \$http_user_agent \$path2 { default /; # 默认路径 ~*v2rayN /v2rayn; # 匹配 V2rayN 客户端 ~*clash /clash2; # 匹配 Clash 客户端 ~*Neko /neko; # 匹配 Neko 客户端 ~*ShadowRocket /shadowrocket; # 匹配 ShadowRocket 客户端 ~*SFM|SFI|SFA /sing-box2; # 匹配 Sing-box pc 和 phone 客户端 # ~*Chrome|Firefox|Mozilla /; # 添加更多的分流规则 } include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' '\$status \$body_bytes_sent "\$http_referer" ' '"\$http_user_agent" "\$http_x_forwarded_for"'; access_log /dev/null; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; #include /etc/nginx/conf.d/*.conf; server { listen $PORT_NGINX ; # ipv4 listen [::]:$PORT_NGINX ; # ipv6 # listen $PORT_NGINX ssl http2 ; # https server_name localhost; # ssl_certificate $WORK_DIR/cert/cert.pem; # ssl_certificate_key $WORK_DIR/cert/private.key; # ssl_protocols TLSv1.3; # ssl_session_tickets on; # ssl_stapling off; # ssl_stapling_verify off; # 来自 /auto2 的分流 location ~ ^/${UUID_CONFIRM}/auto2 { default_type 'text/plain; charset=utf-8'; alias ${WORK_DIR}/subscribe/\$path2; } # 来自 /auto 的分流 location ~ ^/${UUID_CONFIRM}/auto { default_type 'text/plain; charset=utf-8'; alias ${WORK_DIR}/subscribe/\$path1; } location ~ ^/${UUID_CONFIRM}/(.*) { autoindex on; proxy_set_header X-Real-IP \$proxy_protocol_addr; default_type 'text/plain; charset=utf-8'; alias ${WORK_DIR}/subscribe/\$1; } } } EOF } # 生成 sing-box 配置文件 sing-box_json() { local IS_CHANGE=$1 mkdir -p $WORK_DIR/conf $WORK_DIR/logs $WORK_DIR/subscribe # 判断是否为新安装,不为 change 就是新安装 if [ "$IS_CHANGE" = 'change' ]; then # 判断 sing-box 主程序所在路径 DIR=$WORK_DIR else DIR=$TEMP_DIR # 生成 dns 配置 cat > $WORK_DIR/conf/00_log.json << EOF { "log":{ "disabled":false, "level":"error", "output":"$WORK_DIR/logs/box.log", "timestamp":true } } EOF # 生成 outbound 配置 cat > $WORK_DIR/conf/01_outbounds.json << EOF { "outbounds":[ { "type":"direct", "tag":"direct", "domain_strategy":"$DOMAIN_STRATEG" }, { "type":"direct", "tag":"warp-IPv4-out", "detour":"wireguard-out", "domain_strategy":"ipv4_only" }, { "type":"direct", "tag":"warp-IPv6-out", "detour":"wireguard-out", "domain_strategy":"ipv6_only" }, { "type":"wireguard", "tag":"wireguard-out", "server":"${WARP_ENDPOINT}", "server_port":2408, "local_address":[ "172.16.0.2/32", "2606:4700:110:8a36:df92:102a:9602:fa18/128" ], "private_key":"YFYOAdbw1bKTHlNNi+aEjBM3BO7unuFC5rOkMRAz9XY=", "peer_public_key":"bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", "reserved":[ 78, 135, 76 ], "mtu":1280 }, { "type":"block", "tag":"block" } ] } EOF # 生成 route 配置 cat > $WORK_DIR/conf/02_route.json << EOF { "route":{ "rule_set":[ { "tag":"geosite-openai", "type":"remote", "format":"binary", "url":"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-openai.srs" } ], "rules":[ { "domain":"api.openai.com", "outbound":"$CHAT_GPT_OUT_V4" }, { "rule_set":"geosite-openai", "outbound":"$CHAT_GPT_OUT_V6" } ] } } EOF # 生成缓存文件 cat > $WORK_DIR/conf/03_experimental.json << EOF { "experimental": { "cache_file": { "enabled": true, "path": "$WORK_DIR/cache.db" } } } EOF # 生成 dns 配置文件 cat > $WORK_DIR/conf/04_dns.json << EOF { "dns":{ "servers":[ { "address":"local" } ] } } EOF fi # 生成 Reality 公私钥,第一次安装的时候使用新生成的;添加协议的时,使用相应数组里的第一个非空值,如全空则像第一次安装那样使用新生成的 if [[ "${#REALITY_PRIVATE[@]}" = 0 || "${#REALITY_PUBLIC[@]}" = 0 ]]; then REALITY_KEYPAIR=$($DIR/sing-box generate reality-keypair) && REALITY_PRIVATE=$(awk '/PrivateKey/{print $NF}' <<< "$REALITY_KEYPAIR") && REALITY_PUBLIC=$(awk '/PublicKey/{print $NF}' <<< "$REALITY_KEYPAIR") else REALITY_PRIVATE=$(awk '{print $1}' <<< "${REALITY_PRIVATE[@]}") && REALITY_PUBLIC=$(awk '{print $1}' <<< "${REALITY_PUBLIC[@]}") fi # 生成 TLS 网站 [ "${#TLS_SERVER[@]}" -gt 0 ] && TLS_SERVER=$(awk '{print $1}' <<< "${TLS_SERVER[@]}") || TLS_SERVER=$TLS_SERVER_DEFAULT # 第1个协议为 b (a为全部),生成 XTLS + Reality 配置 CHECK_PROTOCOLS=b if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_XTLS_REALITY" ] && PORT_XTLS_REALITY=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[11]=${NODE_NAME[11]:-"$NODE_NAME_CONFIRM"} && UUID[11]=${UUID[11]:-"$UUID_CONFIRM"} && TLS_SERVER[11]=${TLS_SERVER[11]:-"$TLS_SERVER"} && REALITY_PRIVATE[11]=${REALITY_PRIVATE[11]:-"$REALITY_PRIVATE"} && REALITY_PUBLIC[11]=${REALITY_PUBLIC[11]:-"$REALITY_PUBLIC"} cat > $WORK_DIR/conf/11_${NODE_TAG[0]}_inbounds.json << EOF // "public_key":"${REALITY_PUBLIC[11]}" { "inbounds":[ { "type":"vless", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[11]} ${NODE_TAG[0]}", "listen":"::", "listen_port":$PORT_XTLS_REALITY, "users":[ { "uuid":"${UUID[11]}", "flow":"" } ], "tls":{ "enabled":true, "server_name":"${TLS_SERVER[11]}", "reality":{ "enabled":true, "handshake":{ "server":"${TLS_SERVER[11]}", "server_port":443 }, "private_key":"${REALITY_PRIVATE[11]}", "short_id":[ "" ] } }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 Hysteria2 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_HYSTERIA2" ] && PORT_HYSTERIA2=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[12]=${NODE_NAME[12]:-"$NODE_NAME_CONFIRM"} && UUID[12]=${UUID[12]:-"$UUID_CONFIRM"} cat > $WORK_DIR/conf/12_${NODE_TAG[1]}_inbounds.json << EOF { "inbounds":[ { "type":"hysteria2", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[12]} ${NODE_TAG[1]}", "listen":"::", "listen_port":$PORT_HYSTERIA2, "users":[ { "password":"${UUID[12]}" } ], "ignore_client_bandwidth":false, "tls":{ "enabled":true, "server_name":"", "alpn":[ "h3" ], "min_version":"1.3", "max_version":"1.3", "certificate_path":"$WORK_DIR/cert/cert.pem", "key_path":"$WORK_DIR/cert/private.key" } } ] } EOF fi # 生成 Tuic V5 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_TUIC" ] && PORT_TUIC=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[13]=${NODE_NAME[13]:-"$NODE_NAME_CONFIRM"} && UUID[13]=${UUID[13]:-"$UUID_CONFIRM"} && TUIC_PASSWORD=${TUIC_PASSWORD:-"$UUID_CONFIRM"} && TUIC_CONGESTION_CONTROL=${TUIC_CONGESTION_CONTROL:-"bbr"} cat > $WORK_DIR/conf/13_${NODE_TAG[2]}_inbounds.json << EOF { "inbounds":[ { "type":"tuic", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[13]} ${NODE_TAG[2]}", "listen":"::", "listen_port":$PORT_TUIC, "users":[ { "uuid":"${UUID[13]}", "password":"$TUIC_PASSWORD" } ], "congestion_control": "$TUIC_CONGESTION_CONTROL", "zero_rtt_handshake": false, "tls":{ "enabled":true, "alpn":[ "h3" ], "certificate_path":"$WORK_DIR/cert/cert.pem", "key_path":"$WORK_DIR/cert/private.key" } } ] } EOF fi # 生成 ShadowTLS V5 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_SHADOWTLS" ] && PORT_SHADOWTLS=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[14]=${NODE_NAME[14]:-"$NODE_NAME_CONFIRM"} && UUID[14]=${UUID[14]:-"$UUID_CONFIRM"} && TLS_SERVER[14]=${TLS_SERVER[14]:-"$TLS_SERVER"} && SHADOWTLS_PASSWORD=${SHADOWTLS_PASSWORD:-"$($DIR/sing-box generate rand --base64 16)"} && SHADOWTLS_METHOD=${SHADOWTLS_METHOD:-"2022-blake3-aes-128-gcm"} cat > $WORK_DIR/conf/14_${NODE_TAG[3]}_inbounds.json << EOF { "inbounds":[ { "type":"shadowtls", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[14]} ${NODE_TAG[3]}", "listen":"::", "listen_port":$PORT_SHADOWTLS, "detour":"shadowtls-in", "version":3, "users":[ { "password":"${UUID[14]}" } ], "handshake":{ "server":"${TLS_SERVER[14]}", "server_port":443 }, "strict_mode":true }, { "type":"shadowsocks", "tag":"shadowtls-in", "listen":"127.0.0.1", "network":"tcp", "method":"$SHADOWTLS_METHOD", "password":"$SHADOWTLS_PASSWORD", "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 Shadowsocks 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_SHADOWSOCKS" ] && PORT_SHADOWSOCKS=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[15]=${NODE_NAME[15]:-"$NODE_NAME_CONFIRM"} && UUID[15]=${UUID[15]:-"$UUID_CONFIRM"} && SHADOWSOCKS_METHOD=${SHADOWSOCKS_METHOD:-"aes-128-gcm"} cat > $WORK_DIR/conf/15_${NODE_TAG[4]}_inbounds.json << EOF { "inbounds":[ { "type":"shadowsocks", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[15]} ${NODE_TAG[4]}", "listen":"::", "listen_port":$PORT_SHADOWSOCKS, "method":"${SHADOWSOCKS_METHOD}", "password":"${UUID[15]}", "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 Trojan 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_TROJAN" ] && PORT_TROJAN=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[16]=${NODE_NAME[16]:-"$NODE_NAME_CONFIRM"} && TROJAN_PASSWORD=${TROJAN_PASSWORD:-"$UUID_CONFIRM"} cat > $WORK_DIR/conf/16_${NODE_TAG[5]}_inbounds.json << EOF { "inbounds":[ { "type":"trojan", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[16]} ${NODE_TAG[5]}", "listen":"::", "listen_port":$PORT_TROJAN, "users":[ { "password":"$TROJAN_PASSWORD" } ], "tls":{ "enabled":true, "certificate_path":"$WORK_DIR/cert/cert.pem", "key_path":"$WORK_DIR/cert/private.key" }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 vmess + ws 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_VMESS_WS" ] && PORT_VMESS_WS=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[17]=${NODE_NAME[17]:-"$NODE_NAME_CONFIRM"} && UUID[17]=${UUID[17]:-"$UUID_CONFIRM"} && WS_SERVER_IP[17]=${WS_SERVER_IP[17]:-"$SERVER_IP"} && CDN[17]=${CDN[17]:-"$CDN"} && VMESS_WS_PATH=${VMESS_WS_PATH:-"${UUID[17]}-vmess"} cat > $WORK_DIR/conf/17_${NODE_TAG[6]}_inbounds.json << EOF // "WS_SERVER_IP_SHOW": "${WS_SERVER_IP[17]}" // "VMESS_HOST_DOMAIN": "$VMESS_HOST_DOMAIN" // "CDN": "${CDN[17]}" { "inbounds":[ { "type":"vmess", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[17]} ${NODE_TAG[6]}", "listen":"::", "listen_port":$PORT_VMESS_WS, "tcp_fast_open":false, "proxy_protocol":false, "users":[ { "uuid":"${UUID[17]}", "alterId":0 } ], "transport":{ "type":"ws", "path":"/$VMESS_WS_PATH", "max_early_data":2048, "early_data_header_name":"Sec-WebSocket-Protocol" }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 vless + ws + tls 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_VLESS_WS" ] && PORT_VLESS_WS=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[18]=${NODE_NAME[18]:-"$NODE_NAME_CONFIRM"} && UUID[18]=${UUID[18]:-"$UUID_CONFIRM"} && WS_SERVER_IP[18]=${WS_SERVER_IP[18]:-"$SERVER_IP"} && CDN[18]=${CDN[18]:-"$CDN"} && VLESS_WS_PATH=${VLESS_WS_PATH:-"${UUID[18]}-vless"} cat > $WORK_DIR/conf/18_${NODE_TAG[7]}_inbounds.json << EOF // "WS_SERVER_IP_SHOW": "${WS_SERVER_IP[18]}" // "CDN": "${CDN[18]}" { "inbounds":[ { "type":"vless", "sniff_override_destination":true, "sniff":true, "tag":"${NODE_NAME[18]} ${NODE_TAG[7]}", "listen":"::", "listen_port":$PORT_VLESS_WS, "tcp_fast_open":false, "proxy_protocol":false, "users":[ { "name":"sing-box", "uuid":"${UUID[18]}" } ], "transport":{ "type":"ws", "path":"/$VLESS_WS_PATH", "max_early_data":2048, "early_data_header_name":"Sec-WebSocket-Protocol" }, "tls":{ "enabled":true, "server_name":"$VLESS_HOST_DOMAIN", "min_version":"1.3", "max_version":"1.3", "certificate_path":"${WORK_DIR}/cert/cert.pem", "key_path":"${WORK_DIR}/cert/private.key" }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 H2 + Reality 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_H2_REALITY" ] && PORT_H2_REALITY=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[19]=${NODE_NAME[19]:-"$NODE_NAME_CONFIRM"} && UUID[19]=${UUID[19]:-"$UUID_CONFIRM"} && TLS_SERVER[19]=${TLS_SERVER[19]:-"$TLS_SERVER"} && REALITY_PRIVATE[19]=${REALITY_PRIVATE[19]:-"$REALITY_PRIVATE"} && REALITY_PUBLIC[19]=${REALITY_PUBLIC[19]:-"$REALITY_PUBLIC"} cat > $WORK_DIR/conf/19_${NODE_TAG[8]}_inbounds.json << EOF // "public_key":"${REALITY_PUBLIC[19]}" { "inbounds":[ { "type":"vless", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[19]} ${NODE_TAG[8]}", "listen":"::", "listen_port":$PORT_H2_REALITY, "users":[ { "uuid":"${UUID[19]}" } ], "tls":{ "enabled":true, "server_name":"${TLS_SERVER[19]}", "reality":{ "enabled":true, "handshake":{ "server":"${TLS_SERVER[19]}", "server_port":443 }, "private_key":"${REALITY_PRIVATE[19]}", "short_id":[ "" ] } }, "transport": { "type": "http" }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi # 生成 gRPC + Reality 配置 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then [ -z "$PORT_GRPC_REALITY" ] && PORT_GRPC_REALITY=$[START_PORT+$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")] NODE_NAME[20]=${NODE_NAME[20]:-"$NODE_NAME_CONFIRM"} && UUID[20]=${UUID[20]:-"$UUID_CONFIRM"} && TLS_SERVER[20]=${TLS_SERVER[20]:-"$TLS_SERVER"} && REALITY_PRIVATE[20]=${REALITY_PRIVATE[20]:-"$REALITY_PRIVATE"} && REALITY_PUBLIC[20]=${REALITY_PUBLIC[20]:-"$REALITY_PUBLIC"} cat > $WORK_DIR/conf/20_${NODE_TAG[9]}_inbounds.json << EOF // "public_key":"${REALITY_PUBLIC[20]}" { "inbounds":[ { "type":"vless", "sniff":true, "sniff_override_destination":true, "tag":"${NODE_NAME[20]} ${NODE_TAG[9]}", "listen":"::", "listen_port":$PORT_GRPC_REALITY, "users":[ { "uuid":"${UUID[20]}" } ], "tls":{ "enabled":true, "server_name":"${TLS_SERVER[20]}", "reality":{ "enabled":true, "handshake":{ "server":"${TLS_SERVER[20]}", "server_port":443 }, "private_key":"${REALITY_PRIVATE[20]}", "short_id":[ "" ] } }, "transport": { "type": "grpc", "service_name": "grpc" }, "multiplex":{ "enabled":true, "padding":true, "brutal":{ "enabled":true, "up_mbps":1000, "down_mbps":1000 } } } ] } EOF fi } # Sing-box 生成守护进程文件 sing-box_systemd() { SING_BOX_SERVICE="[Unit] Description=sing-box service Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target [Service] User=root Type=simple NoNewPrivileges=yes TimeoutStartSec=0 WorkingDirectory=$WORK_DIR " [[ "$EXPORT_MODE" = '2' && "$IS_CENTOS" != 'CentOS7' ]] && SING_BOX_SERVICE+="ExecStartPre=$(type -p nginx) -c $WORK_DIR/nginx.conf " SING_BOX_SERVICE+="ExecStart=$WORK_DIR/sing-box run -C $WORK_DIR/conf/ ExecReload=/bin/kill -HUP \$MAINPID Restart=on-failure RestartSec=10 LimitNOFILE=infinity [Install] WantedBy=multi-user.target" echo "$SING_BOX_SERVICE" > /etc/systemd/system/sing-box.service } # 获取原有各协议的参数,先清空所有的 key-value fetch_nodes_value() { unset FILE NODE_NAME PORT_XTLS_REALITY UUID TLS_SERVER REALITY_PRIVATE REALITY_PUBLIC PORT_HYSTERIA2 OBFS PORT_TUIC TUIC_PASSWORD TUIC_CONGESTION_CONTROL PORT_SHADOWTLS SHADOWTLS_PASSWORD SHADOWSOCKS_METHOD PORT_SHADOWSOCKS PORT_TROJAN TROJAN_PASSWORD PORT_VMESS_WS VMESS_WS_PATH WS_SERVER_IP WS_SERVER_IP_SHOW VMESS_HOST_DOMAIN CDN PORT_VLESS_WS VLESS_WS_PATH VLESS_HOST_DOMAIN PORT_H2_REALITY PORT_GRPC_REALITY # 获取公共数据 ls $WORK_DIR/conf/*-ws*inbounds.json >/dev/null 2>&1 && SERVER_IP=$(awk -F '"' '/"WS_SERVER_IP_SHOW"/{print $4; exit}' $WORK_DIR/conf/*-ws*inbounds.json) || SERVER_IP=$(grep -A1 '"tag"' $WORK_DIR/list | sed -E '/-ws(-tls)*",$/{N;d}' | awk -F '"' '/"server"/{count++; if (count == 1) {print $4; exit}}') EXISTED_PORTS=$(awk -F ':|,' '/listen_port/{print $2}' $WORK_DIR/conf/*_inbounds.json) START_PORT=$(awk 'NR == 1 { min = $0 } { if ($0 < min) min = $0; count++ } END {print min}' <<< "$EXISTED_PORTS") # 获取 Nginx 端口和路径 [ "$IS_NGINX" = 'is_nginx' ] && local NGINX_JSON=$(cat $WORK_DIR/nginx.conf) && PORT_NGINX=$(awk '/listen/{print $2; exit}' <<< "$NGINX_JSON") && UUID_CONFIRM=$(awk -F '/' '/location ~ \^/{print $2; exit}' <<< "$NGINX_JSON") # 获取 XTLS + Reality key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[0]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[0]}_inbounds.json) && NODE_NAME[11]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[0]}.*/\1/p" <<< "$JSON") && PORT_XTLS_REALITY=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[11]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && TLS_SERVER[11]=$(awk -F '"' '/"server_name"/{print $4}' <<< "$JSON") && REALITY_PRIVATE[11]=$(awk -F '"' '/"private_key"/{print $4}' <<< "$JSON") && REALITY_PUBLIC[11]=$(awk -F '"' '/"public_key"/{print $4}' <<< "$JSON") # 获取 Hysteria2 key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[1]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[1]}_inbounds.json) && NODE_NAME[12]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[1]}.*/\1/p" <<< "$JSON") && PORT_HYSTERIA2=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[12]=$(awk -F '"' '/"password"/{count++; if (count == 1) {print $4; exit}}' <<< "$JSON") # 获取 Tuic V5 key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[2]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[2]}_inbounds.json) && NODE_NAME[13]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[2]}.*/\1/p" <<< "$JSON") && PORT_TUIC=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[13]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && TUIC_PASSWORD=$(awk -F '"' '/"password"/{print $4}' <<< "$JSON") && TUIC_CONGESTION_CONTROL=$(awk -F '"' '/"congestion_control"/{print $4}' <<< "$JSON") # 获取 ShadowTLS key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[3]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[3]}_inbounds.json) && NODE_NAME[14]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[3]}.*/\1/p" <<< "$JSON") && PORT_SHADOWTLS=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[14]=$(awk -F '"' '/"password"/{count++; if (count == 1) {print $4; exit}}' <<< "$JSON") && SHADOWTLS_PASSWORD=$(awk -F '"' '/"password"/{count++; if (count == 2) {print $4; exit}}' <<< "$JSON") && TLS_SERVER[14]=$(awk -F '"' '/"server"/{print $4}' <<< "$JSON") && SHADOWTLS_METHOD=$(awk -F '"' '/"method"/{print $4}' <<< "$JSON") # 获取 Shadowsocks key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[4]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[4]}_inbounds.json) && NODE_NAME[15]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[4]}.*/\1/p" <<< "$JSON") && PORT_SHADOWSOCKS=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[15]=$(awk -F '"' '/"password"/{print $4}' <<< "$JSON") && SHADOWSOCKS_METHOD=$(awk -F '"' '/"method"/{print $4}' <<< "$JSON") # 获取 Trojan key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[5]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[5]}_inbounds.json) && NODE_NAME[16]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[5]}.*/\1/p" <<< "$JSON") && PORT_TROJAN=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && TROJAN_PASSWORD=$(awk -F '"' '/"password"/{print $4}' <<< "$JSON") # 获取 vmess + ws key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[6]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[6]}_inbounds.json) && NODE_NAME[17]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[6]}.*/\1/p" <<< "$JSON") && PORT_VMESS_WS=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[17]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && VMESS_WS_PATH=$(sed -n 's#.*"path":"/\(.*\)",#\1#p' <<< "$JSON") && WS_SERVER_IP[17]=$(awk -F '"' '/"WS_SERVER_IP_SHOW"/{print $4}' <<< "$JSON") && VMESS_HOST_DOMAIN=$(awk -F '"' '/"VMESS_HOST_DOMAIN"/{print $4}' <<< "$JSON") && CDN[17]=$(awk -F '"' '/"CDN"/{print $4}' <<< "$JSON") # 获取 vless + ws + tls key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[7]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[7]}_inbounds.json) && NODE_NAME[18]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[7]}.*/\1/p" <<< "$JSON") && PORT_VLESS_WS=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[18]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && VLESS_WS_PATH=$(sed -n 's#.*"path":"/\(.*\)",#\1#p' <<< "$JSON") && WS_SERVER_IP[18]=$(awk -F '"' '/"WS_SERVER_IP_SHOW"/{print $4}' <<< "$JSON") && VLESS_HOST_DOMAIN=$(awk -F '"' '/"server_name"/{print $4}' <<< "$JSON") && CDN[18]=$(awk -F '"' '/"CDN"/{print $4}' <<< "$JSON") # 获取 H2 + Reality key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[8]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[8]}_inbounds.json) && NODE_NAME[19]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[8]}.*/\1/p" <<< "$JSON") && PORT_H2_REALITY=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[19]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && TLS_SERVER[19]=$(awk -F '"' '/"server"/{print $4}' <<< "$JSON") && REALITY_PRIVATE[19]=$(awk -F '"' '/"private_key"/{print $4}' <<< "$JSON") && REALITY_PUBLIC[19]=$(awk -F '"' '/"public_key"/{print $4}' <<< "$JSON") # 获取 gRPC + Reality key-value [ -s $WORK_DIR/conf/*_${NODE_TAG[9]}_inbounds.json ] && local JSON=$(cat $WORK_DIR/conf/*_${NODE_TAG[9]}_inbounds.json) && NODE_NAME[20]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[9]}.*/\1/p" <<< "$JSON") && PORT_GRPC_REALITY=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[20]=$(awk -F '"' '/"uuid"/{print $4}' <<< "$JSON") && TLS_SERVER[20]=$(awk -F '"' '/"server"/{print $4}' <<< "$JSON") && REALITY_PRIVATE[20]=$(awk -F '"' '/"private_key"/{print $4}' <<< "$JSON") && REALITY_PUBLIC[20]=$(awk -F '"' '/"public_key"/{print $4}' <<< "$JSON") } install_sing-box() { sing-box_variable [ "$EXPORT_MODE" = '2' ] && check_nginx [ ! -d /etc/systemd/system ] && mkdir -p /etc/systemd/system [ ! -d $WORK_DIR/logs ] && mkdir -p $WORK_DIR/logs ssl_certificate [ "$SYSTEM" = 'CentOS' ] && check_firewall_configuration hint "\n $(text 2) " && wait sing-box_json echo "$L" > $WORK_DIR/language cp $TEMP_DIR/sing-box $TEMP_DIR/jq $WORK_DIR [ -x $TEMP_DIR/qrencode ] && cp $TEMP_DIR/qrencode $WORK_DIR # 生成 sing-box systemd 配置文件 sing-box_systemd # 生成 Nginx 配置文件 [ "$EXPORT_MODE" = '2' ] && json_nginx # 如果 Alpine 系统,放到开机自启动 if [ "$SYSTEM" = 'Alpine' ]; then cat > /etc/local.d/sing-box.start << EOF #!/usr/bin/env bash systemctl start sing-box EOF chmod +x /etc/local.d/sing-box.start rc-update add local >/dev/null 2>&1 fi # 等待所有后台进程完成后,再次检测状态,运行 Sing-box check_install check_sing-box_stats } export_list() { IS_INSTALL=$1 check_install #### v1.1.9 处理的 jq 和 qrencode 二进制文件代替系统依赖的问题,此处预计6月30日删除 if [ "$IS_NGINX" = 'is_nginx' ]; then [[ ! -s $WORK_DIR/jq && -s /usr/bin/jq ]] && cp /usr/bin/jq $WORK_DIR/ if [ ! -s $WORK_DIR/qrencode ]; then check_arch wget -qO $WORK_DIR/qrencode https://github.com/fscarmen/client_template/raw/main/qrencode-go/qrencode-go-linux-$QRENCODE_ARCH && chmod +x $WORK_DIR/qrencode fi fi [ "$IS_INSTALL" != 'install' ] && fetch_nodes_value # IPv6 时的 IP 处理 if [[ "$SERVER_IP" =~ : ]]; then SERVER_IP_1="[$SERVER_IP]" SERVER_IP_2="[[$SERVER_IP]]" else SERVER_IP_1="$SERVER_IP" SERVER_IP_2="$SERVER_IP" fi # 生成各订阅文件 # 生成 Clash proxy providers 订阅文件 local CLASH_SUBSCRIBE='proxies:' [ -n "$PORT_XTLS_REALITY" ] && local CLASH_XTLS_REALITY="- {name: \"${NODE_NAME[11]} ${NODE_TAG[0]}\", type: vless, server: ${SERVER_IP}, port: ${PORT_XTLS_REALITY}, uuid: ${UUID[11]}, network: tcp, udp: true, tls: true, servername: ${TLS_SERVER[11]}, client-fingerprint: chrome, reality-opts: {public-key: ${REALITY_PUBLIC[11]}, short-id: \"\"}, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local CLASH_SUBSCRIBE+=" $CLASH_XTLS_REALITY " [ -n "$PORT_HYSTERIA2" ] && local CLASH_HYSTERIA2="- {name: \"${NODE_NAME[12]} ${NODE_TAG[1]}\", type: hysteria2, server: ${SERVER_IP}, port: ${PORT_HYSTERIA2}, up: \"200 Mbps\", down: \"1000 Mbps\", password: ${UUID[12]}, skip-cert-verify: true}" && local CLASH_SUBSCRIBE+=" - {name: \"${NODE_NAME[12]} ${NODE_TAG[1]}\", type: hysteria2, server: ${SERVER_IP}, port: ${PORT_HYSTERIA2}, up: \"200 Mbps\", down: \"1000 Mbps\", password: ${UUID[12]}, skip-cert-verify: true} " [ -n "$PORT_TUIC" ] && local CLASH_TUIC="- {name: \"${NODE_NAME[13]} ${NODE_TAG[2]}\", type: tuic, server: ${SERVER_IP}, port: ${PORT_TUIC}, uuid: ${UUID[13]}, password: ${TUIC_PASSWORD}, alpn: [h3], disable-sni: true, reduce-rtt: true, request-timeout: 8000, udp-relay-mode: native, congestion-controller: $TUIC_CONGESTION_CONTROL, skip-cert-verify: true}" && local CLASH_SUBSCRIBE+=" $CLASH_TUIC " [ -n "$PORT_SHADOWTLS" ] && local CLASH_SHADOWTLS="- {name: \"${NODE_NAME[14]} ${NODE_TAG[3]}\", type: ss, server: ${SERVER_IP}, port: ${PORT_SHADOWTLS}, cipher: $SHADOWTLS_METHOD, password: $SHADOWTLS_PASSWORD, plugin: shadow-tls, client-fingerprint: chrome, plugin-opts: {host: ${TLS_SERVER[14]}, password: \"${UUID[14]}\", version: 3}, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local CLASH_SUBSCRIBE+=" $CLASH_SHADOWTLS " [ -n "$PORT_SHADOWSOCKS" ] && local CLASH_SHADOWSOCKS="- {name: \"${NODE_NAME[15]} ${NODE_TAG[4]}\", type: ss, server: ${SERVER_IP}, port: $PORT_SHADOWSOCKS, cipher: ${SHADOWSOCKS_METHOD}, password: ${UUID[15]}, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local CLASH_SUBSCRIBE+=" $CLASH_SHADOWSOCKS " [ -n "$PORT_TROJAN" ] && local CLASH_TROJAN="- {name: \"${NODE_NAME[16]} ${NODE_TAG[5]}\", type: trojan, server: ${SERVER_IP}, port: $PORT_TROJAN, password: $TROJAN_PASSWORD, client-fingerprint: random, skip-cert-verify: true, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local CLASH_SUBSCRIBE+=" $CLASH_TROJAN " [ -n "$PORT_VMESS_WS" ] && local CLASH_VMESS_WS="- {name: \"${NODE_NAME[17]} ${NODE_TAG[6]}\", type: vmess, server: ${CDN[17]}, port: 80, uuid: ${UUID[17]}, udp: true, tls: false, alterId: 0, cipher: none, skip-cert-verify: true, network: ws, ws-opts: { path: \"/$VMESS_WS_PATH\", headers: {Host: $VMESS_HOST_DOMAIN} }, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local WS_SERVER_IP_SHOW=${WS_SERVER_IP[17]} && local TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && local TYPE_PORT_WS=$PORT_VMESS_WS && local CLASH_SUBSCRIBE+=" $CLASH_VMESS_WS # $(text 52) " [ -n "$PORT_VLESS_WS" ] && local CLASH_VLESS_WS="- {name: \"${NODE_NAME[18]} ${NODE_TAG[7]}\", type: vless, server: ${CDN[18]}, port: 443, uuid: ${UUID[18]}, udp: true, tls: true, servername: $VLESS_HOST_DOMAIN, network: ws, skip-cert-verify: true, ws-opts: { path: \"/$VLESS_WS_PATH\", headers: {Host: $VLESS_HOST_DOMAIN}, max-early-data: 2048, early-data-header-name: Sec-WebSocket-Protocol }, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local WS_SERVER_IP_SHOW=${WS_SERVER_IP[18]} && local TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && local TYPE_PORT_WS=$PORT_VLESS_WS && local CLASH_SUBSCRIBE+=" $CLASH_VLESS_WS # $(text 52) " # Clash 的 H2 传输层未实现多路复用功能,在 Clash.Meta 中更建议使用 gRPC 协议,故不输出相关配置。 https://wiki.metacubex.one/config/proxies/vless/ [ -n "$PORT_H2_REALITY" ] [ -n "$PORT_GRPC_REALITY" ] && local CLASH_GRPC_REALITY="- {name: \"${NODE_NAME[20]} ${NODE_TAG[9]}\", type: vless, server: ${SERVER_IP}, port: ${PORT_GRPC_REALITY}, uuid: ${UUID[20]}, network: grpc, tls: true, udp: true, flow:, client-fingerprint: chrome, servername: ${TLS_SERVER[20]}, grpc-opts: { grpc-service-name: \"grpc\" }, reality-opts: { public-key: ${REALITY_PUBLIC[20]}, short-id: \"\" }, smux: { enabled: true, protocol: 'h2mux', padding: true, max-connections: '8', min-streams: '16', statistic: true, only-tcp: false } }" && local CLASH_SUBSCRIBE+=" $CLASH_GRPC_REALITY " echo -n "${CLASH_SUBSCRIBE}" | sed -E '/^[ ]*#|^--/d' | sed '/^$/d' > $WORK_DIR/subscribe/proxies # 生成 clash 订阅配置文件 # 模板1: 使用 proxy providers wget --no-check-certificate -qO- --tries=3 --timeout=2 ${GH_PROXY}${SUBSCRIBE_TEMPLATE}/clash | sed "s#NODE_NAME#${NODE_NAME_CONFIRM}#g; s#PROXY_PROVIDERS_URL#http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/proxies#" > $WORK_DIR/subscribe/clash # 模板2: 不使用 proxy providers CLASH2_PORT=("$PORT_XTLS_REALITY" "$PORT_HYSTERIA2" "$PORT_TUIC" "$PORT_SHADOWTLS" "$PORT_SHADOWSOCKS" "$PORT_TROJAN" "$PORT_VMESS_WS" "$PORT_VLESS_WS" "$PORT_GRPC_REALITY") CLASH2_PROXY_INSERT=("$CLASH_XTLS_REALITY" "$CLASH_HYSTERIA2" "$CLASH_TUIC" "$CLASH_SHADOWTLS" "$CLASH_SHADOWSOCKS" "$CLASH_TROJAN" "$CLASH_VMESS_WS" "$CLASH_VLESS_WS" "$CLASH_GRPC_REALITY") CLASH2_PROXY_GROUPS_INSERT=("- ${NODE_NAME[11]} ${NODE_TAG[0]}" "- ${NODE_NAME[12]} ${NODE_TAG[1]}" "- ${NODE_NAME[13]} ${NODE_TAG[2]}" "- ${NODE_NAME[14]} ${NODE_TAG[3]}" "- ${NODE_NAME[15]} ${NODE_TAG[4]}" "- ${NODE_NAME[16]} ${NODE_TAG[5]}" "- ${NODE_NAME[17]} ${NODE_TAG[6]}" "- ${NODE_NAME[18]} ${NODE_TAG[7]}" "- ${NODE_NAME[20]} ${NODE_TAG[9]}") CLASH2_YAML=$(wget --no-check-certificate -qO- --tries=3 --timeout=2 ${GH_PROXY}${SUBSCRIBE_TEMPLATE}/clash2) for x in ${!CLASH2_PORT[@]}; do [[ ${CLASH2_PORT[x]} =~ [0-9]+ ]] && CLASH2_YAML=$(sed "/proxy-groups:/i\ ${CLASH2_PROXY_INSERT[x]}" <<< "$CLASH2_YAML") && CLASH2_YAML=$(sed -E "/- name: (♻️ 自动选择|📲 电报消息|💬 OpenAi|📹 油管视频|🎥 奈飞视频|📺 巴哈姆特|📺 哔哩哔哩|🌍 国外媒体|🌏 国内媒体|📢 谷歌FCM|Ⓜ️ 微软Bing|Ⓜ️ 微软云盘|Ⓜ️ 微软服务|🍎 苹果服务|🎮 游戏平台|🎶 网易音乐|🎯 全球直连)|^rules:$/i\ ${CLASH2_PROXY_GROUPS_INSERT[x]}" <<< "$CLASH2_YAML") done echo "$CLASH2_YAML" > $WORK_DIR/subscribe/clash2 # 生成 ShadowRocket 订阅配置文件 [ -n "$PORT_XTLS_REALITY" ] && local SHADOWROCKET_SUBSCRIBE+=" vless://$(echo -n "auto:${UUID[11]}@${SERVER_IP_2}:${PORT_XTLS_REALITY}" | base64 -w0)?remarks=${NODE_NAME[11]} ${NODE_TAG[0]}&obfs=none&tls=1&peer=${TLS_SERVER[11]}&mux=1&pbk=${REALITY_PUBLIC[11]} " [ -n "$PORT_HYSTERIA2" ] && local SHADOWROCKET_SUBSCRIBE+=" hysteria2://${UUID[12]}@${SERVER_IP_2}:${PORT_HYSTERIA2}?insecure=1&obfs=none#${NODE_NAME[12]}%20${NODE_TAG[1]} " [ -n "$PORT_TUIC" ] && local SHADOWROCKET_SUBSCRIBE+=" tuic://${TUIC_PASSWORD}:${UUID[13]}@${SERVER_IP_2}:${PORT_TUIC}?congestion_control=$TUIC_CONGESTION_CONTROL&udp_relay_mode=native&alpn=h3&allow_insecure=1#${NODE_NAME[13]}%20${NODE_TAG[2]} " [ -n "$PORT_SHADOWTLS" ] && local SHADOWROCKET_SUBSCRIBE+=" ss://$(echo -n "$SHADOWTLS_METHOD:$SHADOWTLS_PASSWORD@${SERVER_IP_2}:${PORT_SHADOWTLS}" | base64 -w0)?shadow-tls=$(echo -n "{\"version\":\"3\",\"host\":\"${TLS_SERVER[14]}\",\"password\":\"${UUID[14]}\"}" | base64 -w0)#${NODE_NAME[14]}%20${NODE_TAG[3]} " [ -n "$PORT_SHADOWSOCKS" ] && local SHADOWROCKET_SUBSCRIBE+=" ss://$(echo -n "${SHADOWSOCKS_METHOD}:${UUID[15]}@${SERVER_IP_2}:$PORT_SHADOWSOCKS" | base64 -w0)#${NODE_NAME[15]}%20${NODE_TAG[4]} " [ -n "$PORT_TROJAN" ] && local SHADOWROCKET_SUBSCRIBE+=" trojan://$TROJAN_PASSWORD@${SERVER_IP_1}:$PORT_TROJAN?allowInsecure=1#${NODE_NAME[16]}%20${NODE_TAG[5]} " [ -n "$PORT_VMESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[17]} && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && local SHADOWROCKET_SUBSCRIBE+=" ---------------------------- vmess://$(echo -n "none:${UUID[17]}@${CDN[17]}:80" | base64 -w0)?remarks=${NODE_NAME[17]}%20${NODE_TAG[6]}&obfsParam=$VMESS_HOST_DOMAIN&path=/$VMESS_WS_PATH&obfs=websocket&alterId=0 # $(text 52) " [ -n "$PORT_VLESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[18]} && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && local SHADOWROCKET_SUBSCRIBE+=" ---------------------------- vless://$(echo -n "auto:${UUID[18]}@${CDN[18]}:443" | base64 -w0)?remarks=${NODE_NAME[18]}%20${NODE_TAG[7]}&obfsParam=$VLESS_HOST_DOMAIN&path=/$VLESS_WS_PATH?ed=2048&obfs=websocket&tls=1&peer=$VLESS_HOST_DOMAIN&allowInsecure=1 # $(text 52) " [ -n "$PORT_H2_REALITY" ] && local SHADOWROCKET_SUBSCRIBE+=" ---------------------------- vless://$(echo -n auto:${UUID[19]}@${SERVER_IP_2}:${PORT_H2_REALITY} | base64 -w0)?remarks=${NODE_NAME[19]}%20${NODE_TAG[8]}&path=/&obfs=h2&tls=1&peer=${TLS_SERVER[19]}&alpn=h2&mux=1&pbk=${REALITY_PUBLIC[19]} " [ -n "$PORT_GRPC_REALITY" ] && local SHADOWROCKET_SUBSCRIBE+=" vless://$(echo -n "auto:${UUID[20]}@${SERVER_IP_2}:${PORT_GRPC_REALITY}" | base64 -w0)?remarks=${NODE_NAME[20]}%20${NODE_TAG[9]}&path=grpc&obfs=grpc&tls=1&peer=${TLS_SERVER[20]}&pbk=${REALITY_PUBLIC[20]} " echo -n "$SHADOWROCKET_SUBSCRIBE" | sed -E '/^[ ]*#|^--/d' | sed '/^$/d' | base64 -w0 > $WORK_DIR/subscribe/shadowrocket # 生成 V2rayN 订阅文件 [ -n "$PORT_XTLS_REALITY" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- vless://${UUID[11]}@${SERVER_IP_1}:${PORT_XTLS_REALITY}?encryption=none&security=reality&sni=${TLS_SERVER[11]}&fp=chrome&pbk=${REALITY_PUBLIC[11]}&type=tcp&headerType=none#${NODE_NAME[11]// /%20}%20${NODE_TAG[0]}" [ -n "$PORT_HYSTERIA2" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- hysteria2://${UUID[12]}@${SERVER_IP_1}:${PORT_HYSTERIA2}/?alpn=h3&insecure=1#${NODE_NAME[12]// /%20}%20${NODE_TAG[1]}" [ -n "$PORT_TUIC" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- tuic://${UUID[13]}:${TUIC_PASSWORD}@${SERVER_IP_1}:${PORT_TUIC}?alpn=h3&congestion_control=$TUIC_CONGESTION_CONTROL#${NODE_NAME[13]// /%20}%20${NODE_TAG[2]} # $(text 70)" [ -n "$PORT_SHADOWTLS" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- # $(text 54) { \"log\":{ \"level\":\"warn\" }, \"inbounds\":[ { \"domain_strategy\":\"\", \"listen\":\"127.0.0.1\", \"listen_port\":${PORT_SHADOWTLS}, \"sniff\":true, \"sniff_override_destination\":false, \"tag\": \"ShadowTLS\", \"type\":\"mixed\" } ], \"outbounds\":[ { \"detour\":\"shadowtls-out\", \"domain_strategy\":\"\", \"method\":\"$SHADOWTLS_METHOD\", \"password\":\"$SHADOWTLS_PASSWORD\", \"type\":\"shadowsocks\", \"udp_over_tcp\": false, \"multiplex\": { \"enabled\": true, \"protocol\": \"h2mux\", \"max_connections\": 8, \"min_streams\": 16, \"padding\": true } }, { \"domain_strategy\":\"\", \"password\":\"${UUID[14]}\", \"server\":\"${SERVER_IP}\", \"server_port\":${PORT_SHADOWTLS}, \"tag\": \"shadowtls-out\", \"tls\":{ \"enabled\":true, \"server_name\":\"${TLS_SERVER[14]}\", \"utls\": { \"enabled\": true, \"fingerprint\": \"chrome\" } }, \"type\":\"shadowtls\", \"version\":3 } ] }" [ -n "$PORT_SHADOWSOCKS" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- ss://$(echo -n "${SHADOWSOCKS_METHOD}:${UUID[15]}@${SERVER_IP_1}:$PORT_SHADOWSOCKS" | base64 -w0)#${NODE_NAME[15]// /%20}%20${NODE_TAG[4]}" [ -n "$PORT_TROJAN" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- trojan://$TROJAN_PASSWORD@${SERVER_IP_1}:$PORT_TROJAN?security=tls&type=tcp&headerType=none#${NODE_NAME[16]// /%20}%20${NODE_TAG[5]} # $(text 70)" [ -n "$PORT_VMESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[17]} && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && local V2RAYN_SUBSCRIBE+=" ---------------------------- vmess://$(echo -n "{ \"v\": \"2\", \"ps\": \"${NODE_NAME[17]} ${NODE_TAG[6]}\", \"add\": \"${CDN[18]}\", \"port\": \"80\", \"id\": \"${UUID[18]}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"$VMESS_HOST_DOMAIN\", \"path\": \"/$VMESS_WS_PATH\", \"tls\": \"\", \"sni\": \"\", \"alpn\": \"\" }" | base64 -w0) # $(text 52)" [ -n "$PORT_VLESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[18]} && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && local V2RAYN_SUBSCRIBE+=" ---------------------------- vless://${UUID[18]}@${CDN[18]}:443?encryption=none&security=tls&sni=$VLESS_HOST_DOMAIN&type=ws&host=$VLESS_HOST_DOMAIN&path=%2F$VLESS_WS_PATH%3Fed%3D2048#${NODE_NAME[18]// /%20}%20${NODE_TAG[7]} # $(text 52)" [ -n "$PORT_H2_REALITY" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- vless://${UUID[19]}@${SERVER_IP_1}:${PORT_H2_REALITY}?encryption=none&security=reality&sni=${TLS_SERVER[19]}&fp=chrome&pbk=${REALITY_PUBLIC[19]}&type=http#${NODE_NAME[19]// /%20}%20${NODE_TAG[8]}" [ -n "$PORT_GRPC_REALITY" ] && local V2RAYN_SUBSCRIBE+=" ---------------------------- vless://${UUID[20]}@${SERVER_IP_1}:${PORT_GRPC_REALITY}?encryption=none&security=reality&sni=${TLS_SERVER[20]}&fp=chrome&pbk=${REALITY_PUBLIC[20]}&type=grpc&serviceName=grpc&mode=gun#${NODE_NAME[20]// /%20}%20${NODE_TAG[9]}" echo -n "$V2RAYN_SUBSCRIBE" | sed -E '/^[ ]*#|^[ ]+|^--|^\{|^\}/d' | sed '/^$/d' | base64 -w0 > $WORK_DIR/subscribe/v2rayn # 生成 NekoBox 订阅文件 [ -n "$PORT_XTLS_REALITY" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- vless://${UUID[11]}@${SERVER_IP_1}:${PORT_XTLS_REALITY}?security=reality&sni=${TLS_SERVER[11]}&fp=chrome&pbk=${REALITY_PUBLIC[11]}&type=tcp&encryption=none#${NODE_NAME[11]}%20${NODE_TAG[0]}" [ -n "$PORT_HYSTERIA2" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- hy2://${UUID[12]}@${SERVER_IP_1}:${PORT_HYSTERIA2}?insecure=1#${NODE_NAME[12]} ${NODE_TAG[1]}" [ -n "$PORT_TUIC" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- tuic://${TUIC_PASSWORD}:${UUID[13]}@${SERVER_IP_1}:${PORT_TUIC}?congestion_control=$TUIC_CONGESTION_CONTROL&alpn=h3&udp_relay_mode=native&allow_insecure=1&disable_sni=1#${NODE_NAME[13]} ${NODE_TAG[2]}" [ -n "$PORT_SHADOWTLS" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- nekoray://custom#$(echo -n "{\"_v\":0,\"addr\":\"127.0.0.1\",\"cmd\":[\"\"],\"core\":\"internal\",\"cs\":\"{\n \\\"password\\\": \\\"${UUID[14]}\\\",\n \\\"server\\\": \\\"${SERVER_IP_1}\\\",\n \\\"server_port\\\": ${PORT_SHADOWTLS},\n \\\"tag\\\": \\\"shadowtls-out\\\",\n \\\"tls\\\": {\n \\\"enabled\\\": true,\n \\\"server_name\\\": \\\"${TLS_SERVER[14]}\\\"\n },\n \\\"type\\\": \\\"shadowtls\\\",\n \\\"version\\\": 3\n}\n\",\"mapping_port\":0,\"name\":\"1-tls-not-use\",\"port\":1080,\"socks_port\":0}" | base64 -w0) nekoray://shadowsocks#$(echo -n "{\"_v\":0,\"method\":\"$SHADOWTLS_METHOD\",\"name\":\"2-ss-not-use\",\"pass\":\"$SHADOWTLS_PASSWORD\",\"port\":0,\"stream\":{\"ed_len\":0,\"insecure\":false,\"mux_s\":0,\"net\":\"tcp\"},\"uot\":0}" | base64 -w0)" [ -n "$PORT_SHADOWSOCKS" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- ss://$(echo -n "${SHADOWSOCKS_METHOD}:${UUID[15]}" | base64 -w0)@${SERVER_IP_1}:$PORT_SHADOWSOCKS#${NODE_NAME[15]} ${NODE_TAG[4]}" [ -n "$PORT_TROJAN" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- trojan://$TROJAN_PASSWORD@${SERVER_IP_1}:$PORT_TROJAN?security=tls&allowInsecure=1&fp=random&type=tcp#${NODE_NAME[16]} ${NODE_TAG[5]}" [ -n "$PORT_VMESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[17]} && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && local NEKOBOX_SUBSCRIBE+=" ---------------------------- vmess://$(echo -n "{\"add\":\"${CDN[17]}\",\"aid\":\"0\",\"host\":\"$VMESS_HOST_DOMAIN\",\"id\":\"${UUID[17]}\",\"net\":\"ws\",\"path\":\"/$VMESS_WS_PATH\",\"port\":\"80\",\"ps\":\"${NODE_NAME[17]} ${NODE_TAG[6]}\",\"scy\":\"none\",\"sni\":\"\",\"tls\":\"\",\"type\":\"\",\"v\":\"2\"}" | base64 -w0) # $(text 52)" [ -n "$PORT_VLESS_WS" ] && WS_SERVER_IP_SHOW=${WS_SERVER_IP[18]} && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && local NEKOBOX_SUBSCRIBE+=" ---------------------------- vless://${UUID[18]}@${CDN[18]}:443?security=tls&sni=$VLESS_HOST_DOMAIN&type=ws&path=/$VLESS_WS_PATH?ed%3D2048&host=$VLESS_HOST_DOMAIN&encryption=none#${NODE_NAME[18]}%20${NODE_TAG[7]} # $(text 52)" [ -n "$PORT_H2_REALITY" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- vless://${UUID[19]}@${SERVER_IP_1}:${PORT_H2_REALITY}?security=reality&sni=${TLS_SERVER[19]}&alpn=h2&fp=chrome&pbk=${REALITY_PUBLIC[19]}&type=http&encryption=none#${NODE_NAME[19]}%20${NODE_TAG[8]}" [ -n "$PORT_GRPC_REALITY" ] && local NEKOBOX_SUBSCRIBE+=" ---------------------------- vless://${UUID[20]}@${SERVER_IP_1}:${PORT_GRPC_REALITY}?security=reality&sni=${TLS_SERVER[20]}&fp=chrome&pbk=${REALITY_PUBLIC[20]}&type=grpc&serviceName=grpc&encryption=none#${NODE_NAME[20]}%20${NODE_TAG[9]}" echo -n "$NEKOBOX_SUBSCRIBE" | sed -E '/^[ ]*#|^--/d' | sed '/^$/d' | base64 -w0 > $WORK_DIR/subscribe/neko # 生成 Sing-box 订阅文件 [ -n "$PORT_XTLS_REALITY" ] && local INBOUND_REPLACE+=" { \"type\": \"vless\", \"tag\": \"${NODE_NAME[11]} ${NODE_TAG[0]}\", \"server\":\"${SERVER_IP}\", \"server_port\":${PORT_XTLS_REALITY}, \"uuid\":\"${UUID[11]}\", \"flow\":\"\", \"packet_encoding\":\"xudp\", \"tls\":{ \"enabled\":true, \"server_name\":\"${TLS_SERVER[11]}\", \"utls\":{ \"enabled\":true, \"fingerprint\":\"chrome\" }, \"reality\":{ \"enabled\":true, \"public_key\":\"${REALITY_PUBLIC[11]}\", \"short_id\":\"\" } }, \"multiplex\": { \"enabled\": true, \"protocol\": \"h2mux\", \"max_connections\": 8, \"min_streams\": 16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }," && local NODE_REPLACE+="\"${NODE_NAME[11]} ${NODE_TAG[0]}\"," [ -n "$PORT_HYSTERIA2" ] && local INBOUND_REPLACE+=" { \"type\": \"hysteria2\", \"tag\": \"${NODE_NAME[12]} ${NODE_TAG[1]}\", \"server\": \"${SERVER_IP}\", \"server_port\": ${PORT_HYSTERIA2}, \"up_mbps\": 200, \"down_mbps\": 1000, \"password\": \"${UUID[12]}\", \"tls\": { \"enabled\": true, \"insecure\": true, \"server_name\": \"\", \"alpn\": [ \"h3\" ] } }," && local NODE_REPLACE+="\"${NODE_NAME[12]} ${NODE_TAG[1]}\"," [ -n "$PORT_TUIC" ] && local TUIC_INBOUND=" { \"type\": \"tuic\", \"tag\": \"${NODE_NAME[13]} ${NODE_TAG[2]}\", \"server\": \"${SERVER_IP}\", \"server_port\": ${PORT_TUIC}, \"uuid\": \"${UUID[13]}\", \"password\": \"${TUIC_PASSWORD}\", \"congestion_control\": \"$TUIC_CONGESTION_CONTROL\", \"udp_relay_mode\": \"native\", \"zero_rtt_handshake\": false, \"heartbeat\": \"10s\", \"tls\": { \"enabled\": true, \"insecure\": true, \"server_name\": \"\", \"alpn\": [ \"h3\" ] } }," && local INBOUND_REPLACE+="${TUIC_INBOUND}" && local NODE_REPLACE+="\"${NODE_NAME[13]} ${NODE_TAG[2]}\"," && local INBOUND_INSERT+="${TUIC_INBOUND}" && local NODE_INSERT+=", \"${NODE_NAME[13]} ${NODE_TAG[2]}\"" [ -n "$PORT_SHADOWTLS" ] && local SHADOWTLS_INBOUND=" { \"type\": \"shadowsocks\", \"tag\": \"${NODE_NAME[14]} ${NODE_TAG[3]}\", \"method\": \"$SHADOWTLS_METHOD\", \"password\": \"$SHADOWTLS_PASSWORD\", \"detour\": \"shadowtls-out\", \"udp_over_tcp\": false, \"multiplex\": { \"enabled\": true, \"protocol\": \"h2mux\", \"max_connections\": 8, \"min_streams\": 16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }, { \"type\": \"shadowtls\", \"tag\": \"shadowtls-out\", \"server\": \"${SERVER_IP}\", \"server_port\": ${PORT_SHADOWTLS}, \"version\": 3, \"password\": \"${UUID[14]}\", \"tls\": { \"enabled\": true, \"server_name\": \"${TLS_SERVER[14]}\", \"utls\": { \"enabled\": true, \"fingerprint\": \"chrome\" } } }," && local INBOUND_REPLACE+="${SHADOWTLS_INBOUND}" && local NODE_REPLACE+="\"${NODE_NAME[14]} ${NODE_TAG[3]}\"," && local INBOUND_INSERT+="${SHADOWTLS_INBOUND}" && local NODE_INSERT+=", \"${NODE_NAME[14]} ${NODE_TAG[3]}\"" [ -n "$PORT_SHADOWSOCKS" ] && local INBOUND_REPLACE+=" { \"type\": \"shadowsocks\", \"tag\": \"${NODE_NAME[15]} ${NODE_TAG[4]}\", \"server\": \"${SERVER_IP}\", \"server_port\": $PORT_SHADOWSOCKS, \"method\": \"${SHADOWSOCKS_METHOD}\", \"password\": \"${UUID[15]}\", \"multiplex\": { \"enabled\": true, \"protocol\": \"h2mux\", \"max_connections\": 8, \"min_streams\": 16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }," && local NODE_REPLACE+="\"${NODE_NAME[15]} ${NODE_TAG[4]}\"," [ -n "$PORT_TROJAN" ] && local INBOUND_REPLACE+=" { \"type\": \"trojan\", \"tag\": \"${NODE_NAME[16]} ${NODE_TAG[5]}\", \"server\": \"${SERVER_IP}\", \"server_port\": $PORT_TROJAN, \"password\": \"$TROJAN_PASSWORD\", \"tls\": { \"enabled\":true, \"insecure\": true, \"server_name\":\"\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" } }, \"multiplex\": { \"enabled\":true, \"protocol\":\"h2mux\", \"max_connections\": 8, \"min_streams\": 16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }," && local NODE_REPLACE+="\"${NODE_NAME[16]} ${NODE_TAG[5]}\"," [ -n "$PORT_VMESS_WS" ] && local WS_SERVER_IP_SHOW=${WS_SERVER_IP[17]} && local TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && local TYPE_PORT_WS=$PORT_VMESS_WS && local PROMPT+=" # $(text 52)" && local INBOUND_REPLACE+=" { \"type\": \"vmess\", \"tag\": \"${NODE_NAME[17]} ${NODE_TAG[6]}\", \"server\":\"${CDN[17]}\", \"server_port\":80, \"uuid\":\"${UUID[17]}\", \"transport\": { \"type\":\"ws\", \"path\":\"/$VMESS_WS_PATH\", \"headers\": { \"Host\": \"$VMESS_HOST_DOMAIN\" } }, \"multiplex\": { \"enabled\":true, \"protocol\":\"h2mux\", \"max_streams\":16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }," && local NODE_REPLACE+="\"${NODE_NAME[17]} ${NODE_TAG[6]}\"," [ -n "$PORT_VLESS_WS" ] && local WS_SERVER_IP_SHOW=${WS_SERVER_IP[18]} && local TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && local TYPE_PORT_WS=$PORT_VLESS_WS && local PROMPT+=" # $(text 52)" && local INBOUND_REPLACE+=" { \"type\": \"vless\", \"tag\": \"${NODE_NAME[18]} ${NODE_TAG[7]}\", \"server\":\"${CDN[18]}\", \"server_port\":443, \"uuid\":\"${UUID[18]}\", \"tls\": { \"enabled\":true, \"server_name\":\"$VLESS_HOST_DOMAIN\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" } }, \"transport\": { \"type\":\"ws\", \"path\":\"/$VLESS_WS_PATH\", \"headers\": { \"Host\": \"$VLESS_HOST_DOMAIN\" }, \"max_early_data\":2048, \"early_data_header_name\":\"Sec-WebSocket-Protocol\" }, \"multiplex\": { \"enabled\":true, \"protocol\":\"h2mux\", \"max_streams\":16, \"padding\": true, \"brutal\":{ \"enabled\":true, \"up_mbps\":1000, \"down_mbps\":1000 } } }," && local NODE_REPLACE+="\"${NODE_NAME[18]} ${NODE_TAG[7]}\"," [ -n "$PORT_H2_REALITY" ] && local REALITY_H2_INBOUND=" { \"type\": \"vless\", \"tag\": \"${NODE_NAME[19]} ${NODE_TAG[8]}\", \"server\": \"${SERVER_IP}\", \"server_port\": ${PORT_H2_REALITY}, \"uuid\":\"${UUID[19]}\", \"tls\": { \"enabled\":true, \"server_name\":\"${TLS_SERVER[19]}\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" }, \"reality\":{ \"enabled\":true, \"public_key\":\"${REALITY_PUBLIC[19]}\", \"short_id\":\"\" } }, \"packet_encoding\": \"xudp\", \"transport\": { \"type\": \"http\" } }," && local REALITY_H2_NODE="\"${NODE_NAME[19]} ${NODE_TAG[8]}\"" && local NODE_REPLACE+="${REALITY_H2_NODE}," && local INBOUND_REPLACE+=" ${REALITY_H2_INBOUND}" && local INBOUND_INSERT+=" ${REALITY_H2_INBOUND}" && local NODE_INSERT+=", ${REALITY_H2_NODE}" [ -n "$PORT_GRPC_REALITY" ] && local INBOUND_REPLACE+=" { \"type\": \"vless\", \"tag\": \"${NODE_NAME[20]} ${NODE_TAG[9]}\", \"server\": \"${SERVER_IP}\", \"server_port\": ${PORT_GRPC_REALITY}, \"uuid\":\"${UUID[20]}\", \"tls\": { \"enabled\":true, \"server_name\":\"${TLS_SERVER[20]}\", \"utls\": { \"enabled\":true, \"fingerprint\":\"chrome\" }, \"reality\":{ \"enabled\":true, \"public_key\":\"${REALITY_PUBLIC[20]}\", \"short_id\":\"\" } }, \"packet_encoding\": \"xudp\", \"transport\": { \"type\": \"grpc\", \"service_name\": \"grpc\" } }," && local NODE_REPLACE+="\"${NODE_NAME[20]} ${NODE_TAG[9]}\"," # 模板1 local SING_BOX_JSON1=$(wget --no-check-certificate -qO- --tries=3 --timeout=2 ${GH_PROXY}${SUBSCRIBE_TEMPLATE}/sing-box1) echo $SING_BOX_JSON1 | sed 's#, {[^}]\+"tun-in"[^}]\+}##' | sed "s#\"\",#$INBOUND_REPLACE#; s#\"\"#${NODE_REPLACE%,}#g" | $WORK_DIR/jq > $WORK_DIR/subscribe/sing-box-pc echo $SING_BOX_JSON1 | sed 's# {[^}]\+"mixed"[^}]\+},##; s#, "auto_detect_interface": true##' | sed "s#\"\",#$INBOUND_REPLACE#; s#\"\"#${NODE_REPLACE%,}#g" | $WORK_DIR/jq > $WORK_DIR/subscribe/sing-box-phone # 模板2 local SING_BOX_JSON2=$(wget --no-check-certificate -qO- --tries=3 --timeout=2 ${GH_PROXY}${SUBSCRIBE_TEMPLATE}/sing-box2) echo $SING_BOX_JSON2 | sed "s#\"\",#$INBOUND_REPLACE#; s#\"\"#${NODE_REPLACE%,}#g" | $WORK_DIR/jq > $WORK_DIR/subscribe/sing-box2 # 生成二维码 url 文件 [ "$IS_NGINX" = 'is_nginx' ] && cat > $WORK_DIR/subscribe/qr << EOF $(text 81): $(text 82) 1: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto $(text 82) 2: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2 $(text 80) QRcode: $(text 82) 1: https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto $(text 82) 2: https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2 $(text 82) 1: $($WORK_DIR/qrencode "http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto") $(text 82) 2: $($WORK_DIR/qrencode "http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2") EOF # 生成配置文件 EXPORT_LIST_FILE="******************************************* ┌────────────────┐ │ │ │ $(warning "V2rayN") │ │ │ └────────────────┘ $(info "${V2RAYN_SUBSCRIBE}") ******************************************* ┌────────────────┐ │ │ │ $(warning "ShadowRocket") │ │ │ └────────────────┘ ---------------------------- $(hint "${SHADOWROCKET_SUBSCRIBE}") ******************************************* ┌────────────────┐ │ │ │ $(warning "Clash Meta") │ │ │ └────────────────┘ ---------------------------- $(info "$(sed '1d' <<< "${CLASH_SUBSCRIBE}")") ******************************************* ┌────────────────┐ │ │ │ $(warning "NekoBox") │ │ │ └────────────────┘ $(hint "${NEKOBOX_SUBSCRIBE}") ******************************************* ┌────────────────┐ │ │ │ $(warning "Sing-box") │ │ │ └────────────────┘ ---------------------------- $(info "$(echo "{ \"outbounds\":[ ${INBOUND_REPLACE%,} ] }" | $WORK_DIR/jq) ${PROMPT} $(text 72)") " [ "$IS_NGINX" = 'is_nginx' ] && EXPORT_LIST_FILE+=" ******************************************* $(hint "Index: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/ QR code: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/qr V2rayN $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/v2rayn") $(hint "NekoBox $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/neko") $(hint "Clash $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/clash http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/clash2 sing-box for pc $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/sing-box-pc sing-box for cellphone $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/sing-box-phone SFI / SFA / SFM $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/sing-box2 ShadowRocket $(text 80): http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/shadowrocket") ******************************************* $(info " $(text 81): $(text 82) 1: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto $(text 82) 2: http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2 $(text 80) QRcode: $(text 82) 1: https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto $(text 82) 2: https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2") $(hint "$(text 82) 1:") $($WORK_DIR/qrencode http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto) $(hint "$(text 82) 2:") $($WORK_DIR/qrencode http://${SERVER_IP_1}:${PORT_NGINX}/${UUID_CONFIRM}/auto2) " # 生成并显示节点信息 echo "$EXPORT_LIST_FILE" > $WORK_DIR/list cat $WORK_DIR/list # 显示脚本使用情况数据 hint "\n*******************************************\n\n $(text 55) \n" } # 创建快捷方式 create_shortcut() { cat > $WORK_DIR/sb.sh << EOF #!/usr/bin/env bash bash <(wget --no-check-certificate -qO- https://raw.githubusercontent.com/fscarmen/sing-box/main/sing-box.sh) \$1 EOF chmod +x $WORK_DIR/sb.sh ln -sf $WORK_DIR/sb.sh /usr/bin/sb [ -s /usr/bin/sb ] && info "\n $(text 71) " } # 更换各协议的监听端口 change_start_port() { OLD_PORTS=$(awk -F ':|,' '/listen_port/{print $2}' $WORK_DIR/conf/*) OLD_START_PORT=$(awk 'NR == 1 { min = $0 } { if ($0 < min) min = $0; count++ } END {print min}' <<< "$OLD_PORTS") OLD_CONSECUTIVE_PORTS=$(awk 'END { print NR }' <<< "$OLD_PORTS") enter_start_port $OLD_CONSECUTIVE_PORTS cmd_systemctl disable sing-box for ((a=0; a<$OLD_CONSECUTIVE_PORTS; a++)) do [ -s $WORK_DIR/conf/${CONF_FILES[a]} ] && sed -i "s/\(.*listen_port.*:\)$((OLD_START_PORT+a))/\1$((START_PORT+a))/" $WORK_DIR/conf/* done systemctl enable sing-box sleep 2 export_list [ "$(systemctl is-active sing-box)" = 'active' ] && info " Sing-box $(text 30) $(text 37) " || error " Sing-box $(text 30) $(text 38) " } # 增加或删除协议 change_protocols() { check_install [ "$STATUS" = "$(text 26)" ] && error "\n Sing-box $(text 26) " # 查找已安装的协议,并遍历其在所有协议列表中的名称,获取协议名后存放在 EXISTED_PROTOCOLS; 没有的协议存放在 NOT_EXISTED_PROTOCOLS INSTALLED_PROTOCOLS_LIST=$(awk -F '"' '/"tag":/{print $4}' $WORK_DIR/conf/*_inbounds.json | grep -v 'shadowtls-in' | awk '{print $NF}') for f in ${!NODE_TAG[@]}; do [[ $INSTALLED_PROTOCOLS_LIST =~ "${NODE_TAG[f]}" ]] && EXISTED_PROTOCOLS+=("${PROTOCOL_LIST[f]}") || NOT_EXISTED_PROTOCOLS+=("${PROTOCOL_LIST[f]}"); done # 列出已安装协议 hint "\n $(text 63) (${#EXISTED_PROTOCOLS[@]})" for h in "${!EXISTED_PROTOCOLS[@]}"; do hint " $(asc $[h+97]). ${EXISTED_PROTOCOLS[h]} " done # 从已安装的协议中选择需要删除的协议名,并存放在 REMOVE_PROTOCOLS,把保存的协议的协议存放在 KEEP_PROTOCOLS reading "\n $(text 64) " REMOVE_SELECT # 统一为小写,去掉重复选项,处理不在可选列表里的选项,把特殊符号处理 REMOVE_SELECT=$(sed "s/[^a-$(asc $[${#EXISTED_PROTOCOLS[@]} + 96])]//g" <<< "${REMOVE_SELECT,,}" | awk 'BEGIN{RS=""; FS=""}{delete seen; output=""; for(i=1; i<=NF; i++){ if(!seen[$i]++){ output=output $i } } print output}') for ((j=0; j<${#REMOVE_SELECT}; j++)); do REMOVE_PROTOCOLS+=("${EXISTED_PROTOCOLS[$[$(asc "$(awk "NR==$[j+1] {print}" <<< "$(grep -o . <<< "$REMOVE_SELECT")")") - 97]]}") done for k in "${EXISTED_PROTOCOLS[@]}"; do [[ ! "${REMOVE_PROTOCOLS[@]}" =~ "$k" ]] && KEEP_PROTOCOLS+=("$k") done # 如有未安装的协议,列表显示并选择安装,把增加的协议存在放在 ADD_PROTOCOLS if [ "${#NOT_EXISTED_PROTOCOLS[@]}" -gt 0 ]; then hint "\n $(text 65) (${#NOT_EXISTED_PROTOCOLS[@]}) " for i in "${!NOT_EXISTED_PROTOCOLS[@]}"; do hint " $(asc $[i+97]). ${NOT_EXISTED_PROTOCOLS[i]} " done reading "\n $(text 66) " ADD_SELECT # 统一为小写,去掉重复选项,处理不在可选列表里的选项,把特殊符号处理 ADD_SELECT=$(sed "s/[^a-$(asc $[${#NOT_EXISTED_PROTOCOLS[@]} + 96])]//g" <<< "${ADD_SELECT,,}" | awk 'BEGIN{RS=""; FS=""}{delete seen; output=""; for(i=1; i<=NF; i++){ if(!seen[$i]++){ output=output $i } } print output}') for ((l=0; l<${#ADD_SELECT}; l++)); do ADD_PROTOCOLS+=("${NOT_EXISTED_PROTOCOLS[$[$(asc "$(awk "NR==$[l+1] {print}" <<< "$(grep -o . <<< "$ADD_SELECT")")") - 97]]}") done fi # 重新安装 = 保留 + 新增,如数量为 0 ,则触发卸载 REINSTALL_PROTOCOLS=("${KEEP_PROTOCOLS[@]}" "${ADD_PROTOCOLS[@]}") [ "${#REINSTALL_PROTOCOLS[@]}" = 0 ] && error "\n $(text 73) " # 显示重新安装的协议列表,并确认是否正确 hint "\n $(text 67) (${#REINSTALL_PROTOCOLS[@]}) " [ "${#KEEP_PROTOCOLS[@]}" -gt 0 ] && hint "\n $(text 74) (${#KEEP_PROTOCOLS[@]}) " for r in "${!KEEP_PROTOCOLS[@]}"; do hint " $[r+1]. ${KEEP_PROTOCOLS[r]} " done [ "${#ADD_PROTOCOLS[@]}" -gt 0 ] && hint "\n $(text 75) (${#ADD_PROTOCOLS[@]}) " for r in "${!ADD_PROTOCOLS[@]}"; do hint " $[r+1]. ${ADD_PROTOCOLS[r]} " done reading "\n $(text 68) " CONFIRM [ "${CONFIRM,,}" = 'n' ] && exit 0 # 把确认安装的协议遍历所有协议列表的数组,找出其下标并变为英文小写的形式 for m in "${!REINSTALL_PROTOCOLS[@]}"; do for n in "${!PROTOCOL_LIST[@]}"; do if [ "${REINSTALL_PROTOCOLS[m]}" = "${PROTOCOL_LIST[n]}" ]; then INSTALL_PROTOCOLS+=($(asc $[n+98])) fi done done cmd_systemctl disable sing-box # 获取各节点信息 fetch_nodes_value # 用于新节点的配置信息 UUID_CONFIRM=$(awk '{print $1}' <<< "${UUID[@]} $TROJAN_PASSWORD") for v in "${NODE_NAME[@]}"; do [ -n "$v" ] && NODE_NAME_CONFIRM="$v" && break done [ "${#WS_SERVER_IP[@]}" -gt 0 ] && WS_SERVER_IP_SHOW=$(awk '{print $1}' <<< "${WS_SERVER_IP[@]}") && CDN=$(awk '{print $1}' <<< "${CDN[@]}") # 寻找待删除协议的 inbound 文件名 for o in "${REMOVE_PROTOCOLS[@]}"; do for ((s=0; s<${#PROTOCOL_LIST[@]}; s++)); do [ "$o" = "${PROTOCOL_LIST[s]}" ] && REMOVE_FILE+=("${NODE_TAG[s]}_inbounds.json") done done # 删除不需要的协议配置文件 [ "${#REMOVE_FILE[@]}" -gt 0 ] && for t in "${REMOVE_FILE[@]}"; do rm -f $WORK_DIR/conf/*${t} done # 寻找已存在协议中原有的端口号 for p in "${KEEP_PROTOCOLS[@]}"; do for ((u=0; u<${#PROTOCOL_LIST[@]}; u++)); do [ "$p" = "${PROTOCOL_LIST[u]}" ] && KEEP_PORTS+=("$(awk -F '[:,]' '/listen_port/{print $2}' $WORK_DIR/conf/*${NODE_TAG[u]}_inbounds.json)") done done # 根据全部协议,找到空余的端口号 for q in "${!REINSTALL_PROTOCOLS[@]}"; do [[ ! ${KEEP_PORTS[@]} =~ $[START_PORT + q] ]] && ADD_PORTS+=($[START_PORT + q]) done # 所有协议的端口号 REINSTALL_PORTS=(${KEEP_PORTS[@]} ${ADD_PORTS[@]}) CHECK_PROTOCOLS=b # 获取原始 Reality 配置信息 if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_XTLS_REALITY=${REINSTALL_PORTS[POSITION]} fi # 获取原始 Hysteria2 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_HYSTERIA2=${REINSTALL_PORTS[POSITION]} fi # 获取原始 Tuic V5 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_TUIC=${REINSTALL_PORTS[POSITION]} fi # 获取原始 ShadowTLS 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_SHADOWTLS=${REINSTALL_PORTS[POSITION]} fi # 获取原始 Shadowsocks 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_SHADOWSOCKS=${REINSTALL_PORTS[POSITION]} fi # 获取原始 Trojan 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_TROJAN=${REINSTALL_PORTS[POSITION]} fi # 获取原始 vmess + ws 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then local DOMAIN_ERROR_TIME=5 until [ -n "$VMESS_HOST_DOMAIN" ]; do (( DOMAIN_ERROR_TIME-- )) || true [ "$DOMAIN_ERROR_TIME" != 0 ] && TYPE=VMESS && reading "\n $(text 50) " VMESS_HOST_DOMAIN || error "\n $(text 3) \n" done POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_VMESS_WS=${REINSTALL_PORTS[POSITION]} fi # 获取原始 vless + ws + tls 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then local DOMAIN_ERROR_TIME=5 until [ -n "$VLESS_HOST_DOMAIN" ]; do (( DOMAIN_ERROR_TIME-- )) || true [ "$DOMAIN_ERROR_TIME" != 0 ] && TYPE=VLESS && reading "\n $(text 50) " VLESS_HOST_DOMAIN || error "\n $(text 3) \n" done POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_VLESS_WS=${REINSTALL_PORTS[POSITION]} fi # 获取原始 H2 + Reality 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_H2_REALITY=${REINSTALL_PORTS[POSITION]} fi # 获取原始 gRPC + Reality 配置信息 CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++) if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then POSITION=$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}") PORT_GRPC_REALITY=${REINSTALL_PORTS[POSITION]} fi # 选择可以输入 cdn input_cdn # 生成各协议的 json 文件 sing-box_json change # 运行 sing-box systemctl start sing-box # 再次检测状态,运行 Sing-box check_install check_sing-box_stats export_list } # 卸载 Sing-box 全家桶 uninstall() { if [ -d $WORK_DIR ]; then if [ "$SYSTEM" = 'Alpine' ]; then cmd_systemctl disable sing-box 2>/dev/null # systemctl --version | grep -q 'systemctl.py' && rm -f /bin/systemctl else cmd_systemctl disable sing-box 2>/dev/null fi sleep 1 [[ -s $WORK_DIR/nginx.conf && $(ps -ef | grep 'nginx' | wc -l) -le 1 ]] && reading "\n $(text 83) " REMOVE_NGINX [ "${REMOVE_NGINX,,}" = 'y' ] && ${PACKAGE_UNINSTALL[int]} nginx >/dev/null 2>&1 rm -rf $WORK_DIR $TEMP_DIR /etc/systemd/system/sing-box.service /usr/bin/sb info "\n $(text 16) \n" else error "\n $(text 15) \n" fi # 如果 Alpine 系统,删除开机自启动和python3版systemd if [ "$SYSTEM" = 'Alpine' ]; then rm -f /etc/local.d/sing-box.start rc-update add local >/dev/null 2>&1 [ ! $(ls /etc/systemd/system/*.service) ] && rm -f /bin/systemctl fi } # Sing-box 的最新版本 version() { local VERSION_LATEST=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v-]' '/tag_name/{print $5}' | sort -r | sed -n '1p') local ONLINE=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v]' -v var="tag_name.*$VERSION_LATEST" '$0 ~ var {print $5; exit}') local LOCAL=$($WORK_DIR/sing-box version | awk '/version/{print $NF}') info "\n $(text 40) " [[ -n "$ONLINE" && "$ONLINE" != "$LOCAL" ]] && reading "\n $(text 9) " UPDATE || info " $(text 41) " if [ "${UPDATE,,}" = 'y' ]; then check_system_info wget --no-check-certificate --continue ${GH_PROXY}https://github.com/SagerNet/sing-box/releases/download/v$ONLINE/sing-box-$ONLINE-linux-$SING_BOX_ARCH.tar.gz -qO- | tar xz -C $TEMP_DIR sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box if [ -s $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ]; then cmd_systemctl disable sing-box chmod +x $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box && mv $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box $WORK_DIR/sing-box systemctl enable sing-box && sleep 2 && [ "$(systemctl is-active sing-box)" = 'active' ] && info " Sing-box $(text 28) $(text 37)" || error "Sing-box $(text 28) $(text 38) " else local error "\n $(text 42) " fi fi } # 判断当前 Sing-box 的运行状态,并对应的给菜单和动作赋值 menu_setting() { if [[ "$STATUS" =~ $(text 27)|$(text 28) ]]; then # 查进程号,sing-box 运行时长和内存占用 if [ "$STATUS" = "$(text 28)" ]; then if [ "$SYSTEM" = 'Alpine' ]; then PID=$(pidof sing-box | sed -n 1p) else SYSTEMCTL_STATUS=$(systemctl status sing-box) PID=$(awk '/PID/{print $3}' <<< "$SYSTEMCTL_STATUS") RUNTIME=$(awk '/Active:/{for (i=5;i<=NF;i++)printf("%s ", $i);print ""}' <<< "$SYSTEMCTL_STATUS") fi MEMORY_USAGE="$(awk '/VmRSS/{printf "%.1f\n", $2/1024}' /proc/$PID/status)" fi NOW_PORTS=$(awk -F ':|,' '/listen_port/{print $2}' $WORK_DIR/conf/*) NOW_START_PORT=$(awk 'NR == 1 { min = $0 } { if ($0 < min) min = $0; count++ } END {print min}' <<< "$NOW_PORTS") NOW_CONSECUTIVE_PORTS=$(awk 'END { print NR }' <<< "$NOW_PORTS") [ -s $WORK_DIR/sing-box ] && SING_BOX_VERSION="version: $($WORK_DIR/sing-box version | awk '/version/{print $NF}')" [ -s $WORK_DIR/conf/02_route.json ] && { grep -q 'direct' $WORK_DIR/conf/02_route.json && RETURN_STATUS=$(text 27) || RETURN_STATUS=$(text 28); } OPTION[1]="1 . $(text 29)" [ "$STATUS" = "$(text 28)" ] && OPTION[2]="2 . $(text 27) Sing-box (sb -o)" || OPTION[2]="2 . $(text 28) Sing-box (sb -o)" OPTION[3]="3 . $(text 30)" OPTION[4]="4 . $(text 31)" OPTION[5]="5 . $(text 32)" OPTION[6]="6 . $(text 62)" OPTION[7]="7 . $(text 33)" OPTION[8]="8 . $(text 59)" OPTION[9]="9 . $(text 69)" OPTION[10]="10. $(text 76)" ACTION[1]() { export_list; exit 0; } [ "$STATUS" = "$(text 28)" ] && ACTION[2]() { cmd_systemctl disable sing-box; [[ "$(systemctl is-active sing-box)" =~ 'inactive'|'unknown' ]] && info " Sing-box $(text 27) $(text 37)" || error " Sing-box $(text 27) $(text 38) "; } || ACTION[2]() { cmd_systemctl enable sing-box && [ "$(systemctl is-active sing-box)" = 'active' ] && info " Sing-box $(text 28) $(text 37)" || error " Sing-box $(text 28) $(text 38) "; } ACTION[3]() { change_start_port; exit; } ACTION[4]() { version; exit; } ACTION[5]() { bash <(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh"); exit; } ACTION[6]() { change_protocols; exit; } ACTION[7]() { uninstall; exit; } ACTION[8]() { bash <(wget --no-check-certificate -qO- https://raw.githubusercontent.com/fscarmen/argox/main/argox.sh) -$L; exit; } ACTION[9]() { bash <(wget --no-check-certificate -qO- https://raw.githubusercontent.com/fscarmen/sba/main/sba.sh) -$L; exit; } ACTION[10]() { bash <(wget --no-check-certificate -qO- https://tcp.hy2.sh/); exit; } else OPTION[1]="1. $(text 34)" OPTION[2]="2. $(text 32)" OPTION[3]="3. $(text 59)" OPTION[4]="4. $(text 69)" OPTION[5]="5. $(text 76)" ACTION[1]() { install_sing-box; export_list install; create_shortcut; exit; } ACTION[2]() { bash <(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh"); exit; } ACTION[3]() { bash <(wget --no-check-certificate -qO- https://raw.githubusercontent.com/fscarmen/argox/main/argox.sh) -$L; exit; } ACTION[4]() { bash <(wget --no-check-certificate -qO- https://raw.githubusercontent.com/fscarmen/sba/main/sba.sh) -$L; exit; } ACTION[5]() { bash <(wget --no-check-certificate -qO- https://tcp.hy2.sh/); exit; } fi [ "${#OPTION[@]}" -ge '10' ] && OPTION[0]="0 . $(text 35)" || OPTION[0]="0. $(text 35)" ACTION[0]() { exit; } } menu() { clear echo -e "======================================================================================================================\n" info " $(text 17): $VERSION\n $(text 18): $(text 1)\n $(text 19):\n\t $(text 20): $SYS\n\t $(text 21): $(uname -r)\n\t $(text 22): $SING_BOX_ARCH\n\t $(text 23): $VIRT " info "\t IPv4: $WAN4 $WARPSTATUS4 $COUNTRY4 $ASNORG4 " info "\t IPv6: $WAN6 $WARPSTATUS6 $COUNTRY6 $ASNORG6 " info "\t Sing-box: $STATUS\t $SING_BOX_VERSION " [ -n "$PID" ] && info "\t $(text 56): $PID " [ -n "$RUNTIME" ] && info "\t $(text 57): $RUNTIME " [ -n "$MEMORY_USAGE" ] && info "\t $(text 58): $MEMORY_USAGE MB" [ -n "$NOW_START_PORT" ] && info "\t $(text 45) " echo -e "\n======================================================================================================================\n" for ((b=1;b<${#OPTION[*]};b++)); do hint " ${OPTION[b]} "; done hint " ${OPTION[0]} " reading "\n $(text 24) " CHOOSE # 输入必须是数字且少于等于最大可选项 if grep -qE "^[0-9]{1,2}$" <<< "$CHOOSE" && [ "$CHOOSE" -lt "${#OPTION[*]}" ]; then ACTION[$CHOOSE] else warning " $(text 36) [0-$((${#OPTION[*]}-1))] " && sleep 1 && menu fi } check_cdn statistics_of_run-times # 传参 [[ "${*,,}" =~ '-e' ]] && L=E [[ "${*,,}" =~ '-c' ]] && L=C while getopts ":P:p:OoUuVvNnBbRr" OPTNAME; do case "${OPTNAME,,}" in p ) START_PORT=$OPTARG; select_language; check_install; [ "$STATUS" = "$(text 26)" ] && error "\n Sing-box $(text 26) "; change_start_port; exit 0 ;; o ) select_language; check_system_info; check_install; [ "$STATUS" = "$(text 26)" ] && error "\n Sing-box $(text 26) "; [ "$STATUS" = "$(text 28)" ] && ( cmd_systemctl disable sing-box; [[ "$(systemctl is-active sing-box)" =~ 'inactive'|'unknown' ]] && info "\n Sing-box $(text 27) $(text 37)" ) || ( cmd_systemctl enable sing-box && [ "$(systemctl is-active sing-box)" = 'active' ] && info "\n Sing-box $(text 28) $(text 37)" ); exit 0;; u ) select_language; check_system_info; uninstall; exit 0 ;; n ) select_language; [ ! -s $WORK_DIR/list ] && error " Sing-box $(text 26) "; export_list; exit 0 ;; v ) select_language; check_arch; version; exit 0 ;; b ) select_language; bash <(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh"); exit ;; r ) select_language; check_system_info; change_protocols; exit 0 ;; esac done select_language check_root check_arch check_system_info check_dependencies check_system_ip check_install menu_setting menu