Files
fscarmen-sing-box/sing-box.sh
T

2135 lines
88 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 当前脚本版本号
VERSION='v1.1.0'
# 各变量默认值
GH_PROXY='https://gh-proxy.com/'
TEMP_DIR='/tmp/sing-box'
WORK_DIR='/etc/sing-box'
START_PORT_DEFAULT='8881'
MIN_PORT=1000
MAX_PORT=65520
TLS_SERVER=addons.mozilla.org
CDN_DEFAULT=cn.azhz.eu.org
PROTOCOL_LIST=("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" "cdn.anycast.eu.org" "443.cf.bestl.de" "cfip.gay")
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]="1. Add [ H2 + Reality ] and [ gRPC + Reality ]. Reinstall is required; 2. Use beta verion instead of alpha; 3. Support TCP brutal and add the official install script."
C[1]="1. 增加 [ H2 + Reality ] 和 [ gRPC + Reality ],需要重新安装; 2. 由于 Sing-box 更新极快,将使用 beta 版本替代 alpha; 3. 支持 TCP brutal 并提供官方安装脚本"
E[2]="This project is designed to add sing-box support for multiple protocols to VPS, details: [https://github.com/fscarmen/sing-box]\n Script Features:\n\t • Deploy multiple protocols with one click, there is always one for you!\n\t • Custom ports for nat machine with limited open ports.\n\t • Built-in warp chained proxy to unlock chatGPT.\n\t • No domain name is required.\n\t • Support system: Ubuntu, Debian, CentOS, Alpine and Arch Linux 3.\n\t • Support architecture: AMD,ARM and s390x\n"
C[2]="本项目专为 VPS 添加 sing-box 支持的多种协议, 详细说明: [https://github.com/fscarmen/sing-box]\n 脚本特点:\n\t • 一键部署多协议,总有一款适合你\n\t • 自定义端口,适合有限开放端口的 nat 小鸡\n\t • 内置 warp 链式代理解锁 chatGPT\n\t • 不需要域名\n\t • 智能判断操作系统: Ubuntu 、Debian 、CentOS 、Alpine 和 Arch Linux,请务必选择 LTS 系统\n\t • 支持硬件结构类型: AMD 和 ARM\n"
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]="Please enter VPS IP \(Default is: \${SERVER_IP_DEFAULT}\):"
C[10]="请输入 VPS IP \(默认为: \${SERVER_IP_DEFAULT}\):"
E[11]="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]="请输入开始的端口号,必须是 \${MIN_PORT} - \${MAX_PORT},需要连续\${NUM}个空闲的端口 \(默认为: \${START_PORT_DEFAULT}\):"
E[12]="Please enter UUID \(Default is \${UUID_DEFAULT}\):"
C[12]="请输入 UUID \(默认为 \${UUID_DEFAULT}\):"
E[13]="Please enter the node name. \(Default is \${NODE_NAME_DEFAULT}\):"
C[13]="请输入节点名称 \(默认为 \${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 script"
C[34]="安装脚本"
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]="Select more protocols to install (e.g. hgbd):\n a. all (default)"
C[49]="多选需要安装协议(比如 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}] to domain \[\${TYPE_HOST_DOMAIN}], and set the origin rule to \[\${TYPE_PORT_WS}] in Cloudflare."
C[52]="请在 Cloudflare 绑定 \[\${WS_SERVER_IP}] 的域名为 \[\${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 \$V2RAYN_PROTOCOL configuration file need to be updated for the \$V2RAYN_KERNEL kernel."
C[54]="\$V2RAYN_PROTOCOL 配置文件内容,需要更新 \$V2RAYN_KERNEL 内核"
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]="The full template can be found at: https://t.me/ztvps/37"
C[72]="完整模板可参照: https://t.me/ztvps/37"
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"
# 自定义字体彩色,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:]]/}")
[[ "$ZH" =~ ^\[\".+\"\]$ ]] && cut -d \" -f2 <<< "$ZH"
}
# 脚本当天及累计运行次数统计
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=$(cut -d " " -f1 <<< "$COUNT") &&
TOTAL=$(cut -d " " -f3 <<< "$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
}
check_root() {
[ "$(id -u)" != 0 ] && error "\n $(text 43) \n"
}
check_arch() {
# 判断处理器架构
case $(uname -m) in
aarch64|arm64 ) SING_BOX_ARCH=arm64 ;;
x86_64|amd64 ) [[ "$(awk -F ':' '/flags/{print $2; exit}' /proc/cpuinfo)" =~ avx2 ]] && SING_BOX_ARCH=amd64v3 || SING_BOX_ARCH=amd64 ;;
armv7l ) SING_BOX_ARCH=armv7 ;;
* ) error " $(text 25) "
esac
}
# 查安装及运行状态;状态码: 26 未安装, 27 已安装未运行, 28 运行中
check_install() {
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 ONLINE=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v]' '/tag_name.*beta/{print $5; exit}')
ONLINE=${ONLINE:-'1.7.0-beta.3'}
wget --no-check-certificate -c ${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
[ -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
}&
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
else
systemctl enable --now $APP
fi
elif [ "$ENABLE_DISABLE" = 'disable' ]; then
if [ "$SYSTEM" = 'Alpine' ]; then
systemctl stop $APP
rm -f /etc/local.d/$APP.start
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="$(grep -i pretty_name /etc/os-release | cut -d \" -f2)"
[[ -z "$SYS" && $(type -p hostnamectl) ]] && SYS="$(hostnamectl | grep -i system | cut -d : -f2)"
[[ -z "$SYS" && $(type -p lsb_release) ]] && SYS="$(lsb_release -sd)"
[[ -z "$SYS" && -s /etc/lsb-release ]] && SYS="$(grep -i description /etc/lsb-release | cut -d \" -f2)"
[[ -z "$SYS" && -s /etc/redhat-release ]] && SYS="$(grep . /etc/redhat-release)"
[[ -z "$SYS" && -s /etc/issue ]] && SYS="$(grep . /etc/issue | cut -d '\' -f1 | sed '/^[ ]*$/d')"
REGEX=("debian" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "amazon linux" "arch linux" "alpine" "fedora")
RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Arch" "Alpine" "Fedora")
EXCLUDE=("")
MAJOR=("9" "16" "7" "7" "3" "" "37")
PACKAGE_UPDATE=("apt -y update" "apt -y update" "yum -y update" "yum -y update" "pacman -Sy" "apk update -f" "dnf -y update")
PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -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" "yum -y autoremove" "pacman -Rcnsu --noconfirm" "apk del -f" "dnf -y autoremove")
for int in "${!REGEX[@]}"; do [[ $(tr 'A-Z' 'a-z' <<< "$SYS") =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && break; done
[ -z "$SYSTEM" ] && error " $(text 5) "
# 先排除 EXCLUDE 里包括的特定系统,其他系统需要作大发行版本的比较
for ex in "${EXCLUDE[@]}"; do [[ ! $(tr 'A-Z' 'a-z' <<< "$SYS") =~ $ex ]]; done &&
[[ "$(echo "$SYS" | sed "s/[^0-9.]//g" | cut -d. -f1)" -lt "${MAJOR[int]}" ]] && error " $(text 6) "
}
# 检测 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]{3,4}$ && "$START_PORT" -ge "$MIN_PORT" && "$START_PORT" -le "$MAX_PORT" ]]; then
for port in $(eval echo {$START_PORT..$[START_PORT+NUM-1]}); do
if [ "$SYSTEM" = 'Alpine' ]; then
netstat -an | awk '/:[0-9]+/{print $4}' | awk -F ":" '{print $NF}' | grep -q $port && IN_USED+=("$port")
else
lsof -i:$port >/dev/null 2>&1 && IN_USED+=("$port")
fi
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
# 选择安装的协议,由于选项 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=$(tr '[:upper:]' '[:lower:]' <<< "$CHOOSE_PROTOCOLS")
[[ ! "$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 d in "${!INSTALL_PROTOCOLS[@]}"; do
[ "$d" -ge 9 ] && hint " $[d+1]. ${PROTOCOL_LIST[$(($(asc ${INSTALL_PROTOCOLS[d]}) - 98))]} " || hint " $[d+1] . ${PROTOCOL_LIST[$(($(asc ${INSTALL_PROTOCOLS[d]}) - 98))]} "
done
enter_start_port ${#INSTALL_PROTOCOLS[@]}
fi
# 输入服务器 IP,默认为检测到的服务器 IP,如果全部为空,则提示并退出脚本
[ -z "$SERVER_IP" ] && reading "\n $(text 10) " SERVER_IP
SERVER_IP=${SERVER_IP:-"$SERVER_IP_DEFAULT"} && WS_SERVER_IP=$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
wait
# 输入 UUID ,错误超过 5 次将会退出
UUID_DEFAULT=$($TEMP_DIR/sing-box generate uuid)
[ -z "$UUID" ] && reading "\n $(text 12) " UUID
local UUID_ERROR_TIME=5
until [[ -z "$UUID" || "$UUID" =~ ^[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}$ ]]; do
(( UUID_ERROR_TIME-- )) || true
[ "$UUID_ERROR_TIME" = 0 ] && error "\n $(text 3) \n" || reading "\n $(text 4) \n" UUID
done
UUID=${UUID:-"$UUID_DEFAULT"}
# 输入节点名,以系统的 hostname 作为默认
if [ -s /etc/hostname ]; then
NODE_NAME_DEFAULT="$(cat /etc/hostname)"
elif [ $(type -p hostname) ]; then
NODE_NAME_DEFAULT="$(hostname)"
else
NODE_NAME_DEFAULT="Sing-Box"
fi
reading "\n $(text 13) " NODE_NAME
NODE_NAME="${NODE_NAME:-"$NODE_NAME_DEFAULT"}"
}
check_dependencies() {
# 如果是 Alpine,先升级 wget ,安装 systemctl-py 版
if [ "$SYSTEM" = 'Alpine' ]; then
CHECK_WGET=$(wget 2>&1 | head -n 1)
grep -qi 'busybox' <<< "$CHECK_WGET" && ${PACKAGE_INSTALL[int]} wget >/dev/null 2>&1
DEPS_CHECK=("bash" "python3" "rc-update" "openssl" "virt-what")
DEPS_INSTALL=("bash" "python3" "openrc" "openssl" "virt-what")
for g in "${!DEPS_CHECK[@]}"; do [ ! $(type -p ${DEPS_CHECK[g]}) ] && [[ ! "${DEPS[@]}" =~ "${DEPS_INSTALL[g]}" ]] && DEPS+=(${DEPS_INSTALL[g]}); done
if [ "${#DEPS[@]}" -ge 1 ]; then
info "\n $(text 7) ${DEPS[@]} \n"
${PACKAGE_UPDATE[int]} >/dev/null 2>&1
${PACKAGE_INSTALL[int]} ${DEPS[@]} >/dev/null 2>&1
fi
[ ! $(type -p systemctl) ] && wget --no-check-certificate https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl3.py -O /bin/systemctl && chmod a+x /bin/systemctl
fi
# 检测 Linux 系统的依赖,升级库并重新安装依赖
unset DEPS_CHECK DEPS_INSTALL DEPS
DEPS_CHECK=("wget" "systemctl" "ip" "unzip" "lsof" "bash" "openssl")
DEPS_INSTALL=("wget" "systemctl" "iproute2" "unzip" "lsof" "bash" "openssl")
for g in "${!DEPS_CHECK[@]}"; do [ ! $(type -p ${DEPS_CHECK[g]}) ] && [[ ! "${DEPS[@]}" =~ "${DEPS_INSTALL[g]}" ]] && DEPS+=(${DEPS_INSTALL[g]}); done
if [ "${#DEPS[@]}" -ge 1 ]; then
info "\n $(text 7) ${DEPS[@]} \n"
${PACKAGE_UPDATE[int]} >/dev/null 2>&1
${PACKAGE_INSTALL[int]} ${DEPS[@]} >/dev/null 2>&1
else
info "\n $(text 8) \n"
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")"
}
# 生成 sing-box 配置文件
sing-box_json() {
local IS_CHANGE=$1
mkdir -p $WORK_DIR/conf $WORK_DIR/logs
# 生成 dns 配置
[ "$IS_CHANGE" != 'change' ] && cat > $WORK_DIR/conf/00_log.json << EOF
{
"log":{
"disabled":false,
"level":"error",
"output":"$WORK_DIR/logs/box.log",
"timestamp":true
}
}
EOF
# 生成 outbound 配置
[ "$IS_CHANGE" != 'change' ] && 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 配置
[ "$IS_CHANGE" != 'change' ] && cat > $WORK_DIR/conf/02_route.json << EOF
{
"route":{
"geoip":{
"download_url":"https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db",
"download_detour":"direct"
},
"geosite":{
"download_url":"https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db",
"download_detour":"direct"
},
"rules":[
{
"geosite":[
"openai"
],
"outbound":"warp-IPv6-out"
}
]
}
}
EOF
# 第1个协议为 b (a为全部),生成 XTLS + Reality 配置
CHECK_PROTOCOLS=b
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
[[ -z "$REALITY_PRIVATE" || -z "$REALITY_PUBLIC" ]] && REALITY_KEYPAIR=$($TEMP_DIR/sing-box generate reality-keypair)
[ -z "$REALITY_PRIVATE" ] && REALITY_PRIVATE=$(awk '/PrivateKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -z "$REALITY_PUBLIC" ] && REALITY_PUBLIC=$(awk '/PublicKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -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[*]}")]
cat > $WORK_DIR/conf/11_XTLS_REALITY_inbounds.json << EOF
{
"inbounds":[
{
"type":"vless",
"sniff":true,
"sniff_override_destination":true,
"tag":"xtls-reality-in",
"listen":"::",
"listen_port":$PORT_XTLS_REALITY,
"users":[
{
"uuid":"$UUID",
"flow":"xtls-rprx-vision"
}
],
"tls":{
"enabled":true,
"server_name":"$TLS_SERVER",
"reality":{
"enabled":true,
"handshake":{
"server":"$TLS_SERVER",
"server_port":443
},
"private_key":"$REALITY_PRIVATE",
"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[*]}")]
cat > $WORK_DIR/conf/12_HYSTERIA2_inbounds.json << EOF
{
"inbounds":[
{
"type":"hysteria2",
"sniff":true,
"sniff_override_destination":true,
"tag":"hysteria2-in",
"listen":"::",
"listen_port":$PORT_HYSTERIA2,
"users":[
{
"password":"$UUID"
}
],
"ignore_client_bandwidth":false,
"obfs":{
"type":"salamander",
"password":"$UUID"
},
"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[*]}")]
cat > $WORK_DIR/conf/13_TUIC_inbounds.json << EOF
{
"inbounds":[
{
"type":"tuic",
"sniff":true,
"tag":"tuic-in",
"sniff_override_destination":true,
"listen":"::",
"listen_port":$PORT_TUIC,
"users":[
{
"uuid":"$UUID",
"password":"$UUID"
}
],
"congestion_control": "bbr",
"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 "$SHADOWTLS_PASSWORD" ] && SHADOWTLS_PASSWORD=$($TEMP_DIR/sing-box generate rand --base64 16)
[ -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[*]}")]
cat > $WORK_DIR/conf/14_SHADOWTLS_inbounds.json << EOF
{
"inbounds":[
{
"type":"shadowtls",
"sniff":true,
"sniff_override_destination":true,
"listen":"::",
"listen_port":$PORT_SHADOWTLS,
"detour":"shadowtls-in",
"version":3,
"users":[
{
"password":"$UUID"
}
],
"handshake":{
"server":"$TLS_SERVER",
"server_port":443
},
"strict_mode":true
},
{
"type":"shadowsocks",
"tag":"shadowtls-in",
"listen":"127.0.0.1",
"network":"tcp",
"method":"2022-blake3-aes-128-gcm",
"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[*]}")]
cat > $WORK_DIR/conf/15_SHADOWSOCKS_inbounds.json << EOF
{
"inbounds":[
{
"type":"shadowsocks",
"sniff":true,
"sniff_override_destination":true,
"tag":"shadowsocks-in",
"listen":"::",
"listen_port":$PORT_SHADOWSOCKS,
"method":"aes-128-gcm",
"password":"$UUID",
"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[*]}")]
cat > $WORK_DIR/conf/16_TROJAN_inbounds.json << EOF
{
"inbounds":[
{
"type":"trojan",
"sniff":true,
"sniff_override_destination":true,
"tag":"trojan-in",
"listen":"::",
"listen_port":$PORT_TROJAN,
"users":[
{
"password":"$UUID"
}
],
"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[*]}")]
cat > $WORK_DIR/conf/17_VMESS_WS_inbounds.json << EOF
{
"inbounds":[
{
"type":"vmess",
"sniff":true,
"sniff_override_destination":true,
"tag":"vmess-ws-in",
"listen":"::",
"listen_port":${PORT_VMESS_WS},
"tcp_fast_open":false,
"proxy_protocol":false,
"users":[
{
"uuid":"${UUID}",
"alterId":0
}
],
"transport":{
"type":"ws",
"path":"/${UUID}-vmess",
"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[*]}")]
cat > $WORK_DIR/conf/18_VLESS_WS_inbounds.json << EOF
{
"inbounds":[
{
"type":"vless",
"sniff_override_destination":true,
"sniff":true,
"tag":"vless-ws-in",
"listen":"::",
"listen_port":${PORT_VLESS_WS},
"tcp_fast_open":false,
"proxy_protocol":false,
"users":[
{
"name":"sing-box",
"uuid":"${UUID}"
}
],
"transport":{
"type":"ws",
"path":"/${UUID}-vless",
"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 "$REALITY_PRIVATE" || -z "$REALITY_PUBLIC" ]] && REALITY_KEYPAIR=$($TEMP_DIR/sing-box generate reality-keypair)
[ -z "$REALITY_PRIVATE" ] && REALITY_PRIVATE=$(awk '/PrivateKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -z "$REALITY_PUBLIC" ] && REALITY_PUBLIC=$(awk '/PublicKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -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[*]}")]
cat > $WORK_DIR/conf/19_H2_REALITY_inbounds.json << EOF
{
"inbounds":[
{
"type":"vless",
"sniff":true,
"sniff_override_destination":true,
"tag":"h2-reality-in",
"listen":"::",
"listen_port":$PORT_H2_REALITY,
"users":[
{
"uuid":"$UUID"
}
],
"tls":{
"enabled":true,
"server_name":"$TLS_SERVER",
"reality":{
"enabled":true,
"handshake":{
"server":"$TLS_SERVER",
"server_port":443
},
"private_key":"$REALITY_PRIVATE",
"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 "$REALITY_PRIVATE" || -z "$REALITY_PUBLIC" ]] && REALITY_KEYPAIR=$($TEMP_DIR/sing-box generate reality-keypair)
[ -z "$REALITY_PRIVATE" ] && REALITY_PRIVATE=$(awk '/PrivateKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -z "$REALITY_PUBLIC" ] && REALITY_PUBLIC=$(awk '/PublicKey/{print $NF}' <<< "$REALITY_KEYPAIR")
[ -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[*]}")]
cat > $WORK_DIR/conf/20_GRPC_REALITY_inbounds.json << EOF
{
"inbounds":[
{
"type":"vless",
"sniff":true,
"sniff_override_destination":true,
"tag":"grpc-reality-in",
"listen":"::",
"listen_port":$PORT_GRPC_REALITY,
"users":[
{
"uuid":"$UUID"
}
],
"tls":{
"enabled":true,
"server_name":"$TLS_SERVER",
"reality":{
"enabled":true,
"handshake":{
"server":"$TLS_SERVER",
"server_port":443
},
"private_key":"$REALITY_PRIVATE",
"short_id":[
""
]
}
},
"transport": {
"type": "grpc",
"service_name": "grpc"
}
}
]
}
EOF
fi
}
# Sing-box 生成守护进程文件
sing-box_systemd() {
cat > /etc/systemd/system/sing-box.service << EOF
[Unit]
Description=sing-box service
Documentation=https://sing-box.sagernet.org
After=network.target nss-lookup.target
[Service]
User=root
WorkingDirectory=$WORK_DIR
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
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
EOF
}
install_sing-box() {
sing-box_variable
[ ! -d /etc/systemd/system ] && mkdir -p /etc/systemd/system
[ ! -d $WORK_DIR/logs ] && mkdir -p $WORK_DIR/logs
ssl_certificate
sing-box_json
echo "$L" > $WORK_DIR/language
cp $TEMP_DIR/sing-box $WORK_DIR
sing-box_systemd
# 如果 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() {
check_install
if [ -s $WORK_DIR/list ]; then
SERVER_IP=${SERVER_IP:-"$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | sed -n "s/.*{name.*server:[ ]*\([^,]\+\).*/\1/pg" | sed -n '1p')"}
UUID=${UUID:-"$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | grep -m1 '{name' | sed -En 's/.*password:[ ]+[\"]*|.*uuid:[ ]+[\"]*(.*)/\1/gp' | sed "s/\([^,\"]\+\).*/\1/g")"}
SHADOWTLS_PASSWORD=${SHADOWTLS_PASSWORD:-"$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | sed -n 's/.*{name.*password:[ ]*\"\([^\"]\+\)".*shadow-tls.*/\1/pg')"}
TLS_SERVER=${TLS_SERVER:-"$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | grep -Em1 '\{name.*shadow-tls|\{name.*public-key' | sed -n "s/.*servername:[ ]\+\([^\,]\+\).*/\1/gp; s/.*host:[ ]\+\"\([^\"]\+\).*/\1/gp")"}
NODE_NAME=${NODE_NAME:-"$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | grep -m1 '{name' | sed 's/- {name:[ ]\+"//; s/[ ]\+ShadowTLS[ ]\+v3.*//; s/[ ]\+xtls-reality-vision.*//; s/[ ]\+hysteria2.*//; s/[ ]\+tuic.*//; s/[ ]\+ss.*//; s/[ ]\+trojan.*//; s/[ ]\+trojan.*//; s/[ ]\+vmess[ ]\+ws.*//; s/[ ]\+vless[ ]\+.*//')"}
REALITY_PUBLIC=${REALITY_PUBLIC:-"$(sed -n 's/.*{name.*public-key:[ ]*\([^,]\+\).*/\1/pg' $WORK_DIR/list | sed -n 1p)"}
[ -s $WORK_DIR/conf/*_XTLS_REALITY_inbounds.json ] && PORT_XTLS_REALITY=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_XTLS_REALITY_inbounds.json)
[ -s $WORK_DIR/conf/*_HYSTERIA2_inbounds.json ] && PORT_HYSTERIA2=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_HYSTERIA2_inbounds.json)
[ -s $WORK_DIR/conf/*_TUIC_inbounds.json ] && PORT_TUIC=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_TUIC_inbounds.json)
[ -s $WORK_DIR/conf/*_SHADOWTLS_inbounds.json ] && PORT_SHADOWTLS=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_SHADOWTLS_inbounds.json)
[ -s $WORK_DIR/conf/*_SHADOWSOCKS_inbounds.json ] && PORT_SHADOWSOCKS=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_SHADOWSOCKS_inbounds.json)
[ -s $WORK_DIR/conf/*_TROJAN_inbounds.json ] && PORT_TROJAN=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_TROJAN_inbounds.json)
[ -s $WORK_DIR/conf/*_VMESS_WS_inbounds.json ] && WS_SERVER_IP=${WS_SERVER_IP:-"$(grep -A2 "{name.*vmess[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $2}')"} && VMESS_HOST_DOMAIN=${VMESS_HOST_DOMAIN:-"$(grep -A2 "{name.*vmess[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $4}')"} && PORT_VMESS_WS=${PORT_VMESS_WS:-"$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_VMESS_WS_inbounds.json)"} && CDN=${CDN:-"$(sed -n "s/.*{name.*vmess[ ]\+ws.*server:[ ]\+\([^,]\+\).*/\1/gp" $WORK_DIR/list)"}
[ -s $WORK_DIR/conf/*_VLESS_WS_inbounds.json ] && WS_SERVER_IP=${WS_SERVER_IP:-"$(grep -A2 "{name.*vless[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $2}')"} && VLESS_HOST_DOMAIN=${VLESS_HOST_DOMAIN:-"$(grep -A2 "{name.*vless[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $4}')"} && PORT_VLESS_WS=${PORT_VLESS_WS:-"$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_VLESS_WS_inbounds.json)"} && CDN=${CDN:-"$(sed -n "s/.*{name.*vless[ ]\+ws.*server:[ ]\+\([^,]\+\).*/\1/gp" $WORK_DIR/list)"}
[ -s $WORK_DIR/conf/*_H2_REALITY_inbounds.json ] && PORT_H2_REALITY=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_H2_REALITY_inbounds.json)
[ -s $WORK_DIR/conf/*_GRPC_REALITY_inbounds.json ] && PORT_GRPC_REALITY=$(sed -n '/listen_port/s/[^0-9]\+//gp' $WORK_DIR/conf/*_GRPC_REALITY_inbounds.json)
fi
# 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
# 生成配置文件
cat > $WORK_DIR/list << EOF
*******************************************
┌────────────────┐
│ │
│ $(warning "V2rayN") │
│ │
└────────────────┘
EOF
[ -n "$PORT_XTLS_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "vless://${UUID}@${SERVER_IP_1}:${PORT_XTLS_REALITY}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=tcp&headerType=none#${NODE_NAME} xtls-reality-vision")
EOF
[ -n "$PORT_HYSTERIA2" ] && V2RAYN_PROTOCOL=Hysteria2 && V2RAYN_KERNEL=hysteria2 && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "$(text 54)
server: \"${SERVER_IP_1}:${PORT_HYSTERIA2}\"
auth: ${UUID}
bandwidth:
up: 200 mbps
down: 1000 mbps
obfs:
type: salamander
salamander:
password: ${UUID}
tls:
insecure: true
socks5:
listen: 127.0.0.1:${PORT_HYSTERIA2}")
EOF
[ -n "$PORT_TUIC" ] && V2RAYN_PROTOCOL=Tuic && V2RAYN_KERNEL=sing_box && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "$(text 54)
{
\"log\":{
\"level\":\"info\"
},
\"inbounds\":[
{
\"domain_strategy\":\"\",
\"listen\":\"127.0.0.1\",
\"listen_port\":${PORT_TUIC},
\"sniff\":true,
\"sniff_override_destination\":false,
\"type\":\"mixed\"
}
],
\"outbounds\":[
{
\"congestion_control\":\"bbr\",
\"domain_strategy\":\"\",
\"heartbeat\":\"10s\",
\"password\":\"${UUID}\",
\"server\":\"${SERVER_IP}\",
\"server_port\":${PORT_TUIC},
\"tag\": \"proxy\",
\"tls\":{
\"alpn\":[
\"h3\",
\"spdy/3.1\"
],
\"certificate\":\"\",
\"disable_sni\":false,
\"enabled\":true,
\"insecure\":true,
\"server_name\":\"\"
},
\"type\":\"tuic\",
\"udp_relay_mode\":\"native\",
\"uuid\":\"${UUID}\",
\"zero_rtt_handshake\":false
}
]
}")
EOF
[ -n "$PORT_SHADOWTLS" ] && V2RAYN_PROTOCOL=ShadowTLS && V2RAYN_KERNEL=sing_box && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "$(text 54)
{
\"log\":{
\"level\":\"info\"
},
\"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\":\"2022-blake3-aes-128-gcm\",
\"password\":\"${SHADOWTLS_PASSWORD}\",
\"type\":\"shadowsocks\",
\"udp_over_tcp\": false,
\"multiplex\": {
\"enabled\": true,
\"protocol\": \"h2mux\",
\"max_connections\": 16,
\"padding\": true
}
},
{
\"domain_strategy\":\"\",
\"password\":\"${UUID}\",
\"server\":\"${SERVER_IP}\",
\"server_port\":${PORT_SHADOWTLS},
\"tag\": \"shadowtls-out\",
\"tls\":{
\"enabled\":true,
\"server_name\":\"${TLS_SERVER}\",
\"utls\": {
\"enabled\": true,
\"fingerprint\": \"chrome\"
}
},
\"multiplex\":{
\"enabled\":true,
\"padding\":true
},
\"type\":\"shadowtls\",
\"version\":3
}
]
}")
EOF
[ -n "$PORT_SHADOWSOCKS" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "ss://$(base64 -w0 <<< aes-128-gcm:${UUID}@${SERVER_IP_1}:${PORT_SHADOWSOCKS})#${NODE_NAME} ss")
EOF
[ -n "$PORT_TROJAN" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "trojan://${UUID}@${SERVER_IP_1}:${PORT_TROJAN}?security=tls&type=tcp&headerType=none#\"${NODE_NAME} trojan\"
$(text 70)")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "vmess://$(base64 -w0 <<< "{ \"v\": \"2\", \"ps\": \"${NODE_NAME} vmess ws\", \"add\": \"${CDN}\", \"port\": \"80\", \"id\": \"${UUID}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"${VMESS_HOST_DOMAIN}\", \"path\": \"/${UUID}-vmess\", \"tls\": \"\", \"sni\": \"\", \"alpn\": \"\" }" | sed "s/Cg==$//")
$(text 52)")
EOF
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "vless://${UUID}@${CDN}:443?encryption=none&security=tls&sni=${VLESS_HOST_DOMAIN}&type=ws&host=${VLESS_HOST_DOMAIN}&path=%2F${UUID}-vless%3Fed%3D2048#${NODE_NAME} vless ws
$(text 52)")
EOF
[ -n "$PORT_H2_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "vless://${UUID}@${SERVER_IP_1}:${PORT_H2_REALITY}?encryption=none&security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=http#${NODE_NAME} h2-reality-vision")
EOF
[ -n "$PORT_GRPC_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(info "vless://${UUID}@${SERVER_IP_1}:${PORT_GRPC_REALITY}?encryption=none&security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=grpc&serviceName=grpc&mode=gun#${NODE_NAME} grpc-reality-vision")
EOF
cat >> $WORK_DIR/list << EOF
*******************************************
┌────────────────┐
│ │
│ $(warning "Shadowrocket") │
│ │
└────────────────┘
----------------------------
EOF
[ -n "$PORT_XTLS_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(hint "vless://$(base64 -w0 <<< auto:$UUID@${SERVER_IP_2}:${PORT_XTLS_REALITY} | sed "s/Cg==$//")?remarks=${NODE_NAME}%20xtls-reality-vision&obfs=none&tls=1&peer=$TLS_SERVER&xtls=2&pbk=$REALITY_PUBLIC")
EOF
[ -n "$PORT_HYSTERIA2" ] && cat >> $WORK_DIR/list << EOF
$(hint "hysteria2://${UUID}@${SERVER_IP_2}:${PORT_HYSTERIA2}?insecure=1&obfs=none&obfs-password=${UUID}#${NODE_NAME}%20hysteria2")
EOF
[ -n "$PORT_TUIC" ] && cat >> $WORK_DIR/list << EOF
$(hint "tuic://${UUID}:${UUID}@${SERVER_IP_2}:${PORT_TUIC}?congestion_control=bbr&udp_relay_mode=native&alpn=h3&allow_insecure=1#${NODE_NAME}%20tuic")
EOF
[ -n "$PORT_SHADOWTLS" ] && cat >> $WORK_DIR/list << EOF
$(hint "ss://$(base64 -w0 <<< 2022-blake3-aes-128-gcm:${SHADOWTLS_PASSWORD}@${SERVER_IP_2}:${PORT_SHADOWTLS} | sed "s/Cg==$//")?shadow-tls=$(base64 -w0 <<< {\"version\":\"3\",\"host\":\"$TLS_SERVER\",\"password\":\"$UUID\" | sed "s/Cg==$//")#${NODE_NAME}%20ShadowTLS%20v3")
EOF
[ -n "$PORT_SHADOWSOCKS" ] && cat >> $WORK_DIR/list << EOF
$(hint "ss://$(base64 -w0 <<< aes-128-gcm:${UUID}@${SERVER_IP_2}:${PORT_SHADOWSOCKS} | sed "s/Cg==$//")#${NODE_NAME}%20ss")
EOF
[ -n "$PORT_TROJAN" ] && cat >> $WORK_DIR/list << EOF
$(hint "trojan://${UUID}@${SERVER_IP_1}:${PORT_TROJAN}?allowInsecure=1#${NODE_NAME}%20trojan")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vmess://$(base64 -w0 <<< "none:${UUID}@${CDN}:80" | sed "s/Cg==$//")?remarks=${NODE_NAME}%20vmess%20ws&obfsParam=${VMESS_HOST_DOMAIN}&path=/${UUID}-vmess&obfs=websocket&alterId=0
$(text 52)")
EOF
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://$(base64 -w0 <<< "auto:${UUID}@${CDN}:443" | sed "s/Cg==$//")?remarks=${NODE_NAME}%20vless%20ws&obfsParam=${VLESS_HOST_DOMAIN}&path=/${UUID}-vless?ed=2048&obfs=websocket&tls=1&peer=${VLESS_HOST_DOMAIN}&allowInsecure=1
$(text 52)")
EOF
[ -n "$PORT_H2_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://$(base64 -w0 <<< auto:${UUID}@${SERVER_IP_2}:${PORT_H2_REALITY} | sed "s/Cg==$//")?remarks=${NODE_NAME}%20h2-reality-vision&path=/&obfs=h2&tls=1&peer=${TLS_SERVER}&alpn=h2&mux=1&pbk=${REALITY_PUBLIC}")
EOF
[ -n "$PORT_GRPC_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://$(base64 -w0 <<< "auto:${UUID}@${SERVER_IP_2}:${PORT_GRPC_REALITY}" | sed "s/Cg==$//")?remarks=${NODE_NAME}%20grpc-reality-vision&path=grpc&obfs=grpc&tls=1&peer=${TLS_SERVER}&pbk=${REALITY_PUBLIC}")
EOF
cat >> $WORK_DIR/list << EOF
*******************************************
┌────────────────┐
│ │
│ $(warning "Clash Meta") │
│ │
└────────────────┘
----------------------------
EOF
[ -n "$PORT_XTLS_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} xtls-reality-vision\", type: vless, server: ${SERVER_IP}, port: ${PORT_XTLS_REALITY}, uuid: ${UUID}, network: tcp, udp: true, tls: true, servername: ${TLS_SERVER}, flow: xtls-rprx-vision, client-fingerprint: chrome, reality-opts: {public-key: ${REALITY_PUBLIC}, short-id: \"\"} }")
EOF
[ -n "$PORT_HYSTERIA2" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} hysteria2\", type: hysteria2, server: ${SERVER_IP}, port: ${PORT_HYSTERIA2}, up: \"200 Mbps\", down: \"1000 Mbps\", password: ${UUID}, obfs: salamander, obfs-password: ${UUID}, skip-cert-verify: true}")
EOF
[ -n "$PORT_TUIC" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} tuic\", type: tuic, server: ${SERVER_IP}, port: ${PORT_TUIC}, uuid: ${UUID}, password: ${UUID}, alpn: [h3], disable-sni: true, reduce-rtt: true, request-timeout: 8000, udp-relay-mode: native, congestion-controller: bbr, skip-cert-verify: true}")
EOF
[ -n "$PORT_SHADOWTLS" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} ShadowTLS v3\", type: ss, server: ${SERVER_IP}, port: ${PORT_SHADOWTLS}, cipher: 2022-blake3-aes-128-gcm, password: \"${SHADOWTLS_PASSWORD}\", plugin: shadow-tls, client-fingerprint: chrome, plugin-opts: {host: \"${TLS_SERVER}\", password: \"${UUID}\", version: 3}}")
EOF
[ -n "$PORT_SHADOWSOCKS" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} ss\", type: ss, server: ${SERVER_IP}, port: ${PORT_SHADOWSOCKS}, cipher: aes-128-gcm, password: \"${UUID}\"}")
EOF
[ -n "$PORT_TROJAN" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} trojan\", type: trojan, server: ${SERVER_IP}, port: ${PORT_TROJAN}, password: ${UUID}, client-fingerprint: random, skip-cert-verify: true}")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} vmess ws\", type: vmess, server: ${CDN}, port: 80, uuid: ${UUID}, udp: true, tls: false, alterId: 0, cipher: none, skip-cert-verify: true, network: ws, ws-opts: { path: \"/${UUID}-vmess\", headers: { Host: ${VMESS_HOST_DOMAIN}, max-early-data: 2048, early-data-header-name: Sec-WebSocket-Protocol} } }
$(text 52)")
EOF
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} vless ws\", type: vless, server: ${CDN}, port: 443, uuid: ${UUID}, udp: true, tls: true, servername: ${VLESS_HOST_DOMAIN}, network: ws, skip-cert-verify: true, ws-opts: { path: \"/${UUID}-vless?ed=2048\", headers: { Host: ${VLESS_HOST_DOMAIN} } } }
$(text 52)")
EOF
[ -n "$PORT_GRPC_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(info "- {name: \"${NODE_NAME} vless-reality-grpc\", type: vless, server: ${SERVER_IP}, port: ${PORT_GRPC_REALITY}, uuid: ${UUID}, network: grpc, tls: true, udp: true, flow:, client-fingerprint: chrome, servername: ${TLS_SERVER}, grpc-opts: { grpc-service-name: \"grpc\" }, reality-opts: { public-key: ${REALITY_PUBLIC}, short-id: \"\" } }")
EOF
cat >> $WORK_DIR/list << EOF
*******************************************
┌────────────────┐
│ │
│ $(warning "NekoBox") │
│ │
└────────────────┘
EOF
[ -n "$PORT_XTLS_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://${UUID}@${SERVER_IP_1}:${PORT_XTLS_REALITY}?security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=tcp&flow=xtls-rprx-vision&encryption=none#${NODE_NAME}%20xtls-reality-vision")
EOF
[ -n "$PORT_HYSTERIA2" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "hy2://${UUID}@${SERVER_IP_1}:${PORT_HYSTERIA2}?obfs=salamander&obfs-password=${UUID}&insecure=1#${NODE_NAME}%20hysteria2")
EOF
[ -n "$PORT_TUIC" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "tuic://${UUID}:${UUID}@${SERVER_IP_1}:${PORT_TUIC}?congestion_control=bbr&alpn=h3&udp_relay_mode=native&allow_insecure=1&disable_sni=1#${NODE_NAME}%20tuic")
EOF
[ -n "$PORT_SHADOWTLS" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "nekoray://custom#$(base64 -w0 <<< "{\"_v\":0,\"addr\":\"127.0.0.1\",\"cmd\":[\"\"],\"core\":\"internal\",\"cs\":\"{\n \\\"password\\\": \\\"${UUID}\\\",\n \\\"server\\\": \\\"${SERVER_IP_1}\\\",\n \\\"server_port\\\": ${PORT_SHADOWTLS},\n \\\"tag\\\": \\\"shadowtls-out\\\",\n \\\"tls\\\": {\n \\\"enabled\\\": true,\n \\\"server_name\\\": \\\"addons.mozilla.org\\\"\n },\n \\\"type\\\": \\\"shadowtls\\\",\n \\\"version\\\": 3\n}\n\",\"mapping_port\":0,\"name\":\"1-tls-not-use\",\"port\":1080,\"socks_port\":0}")
nekoray://shadowsocks#$(base64 -w0 <<< "{\"_v\":0,\"method\":\"2022-blake3-aes-128-gcm\",\"name\":\"2-ss-not-use\",\"pass\":\"${SHADOWTLS_PASSWORD}\",\"port\":0,\"stream\":{\"ed_len\":0,\"insecure\":false,\"mux_s\":0,\"net\":\"tcp\"},\"uot\":0}")
$(text 48)")
EOF
[ -n "$PORT_SHADOWSOCKS" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "ss://$(base64 -w0 <<< aes-128-gcm:${UUID} | sed "s/Cg==$//")@${SERVER_IP_1}:${PORT_SHADOWSOCKS}#${NODE_NAME}%20ss")
EOF
[ -n "$PORT_TROJAN" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "trojan://${UUID}@${SERVER_IP_1}:${PORT_TROJAN}?security=tls&allowInsecure=1&fp=random&type=tcp#${NODE_NAME}%20trojan")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vmess://$(base64 -w0 <<< "{\"add\":\"${CDN}\",\"aid\":\"0\",\"host\":\"${VMESS_HOST_DOMAIN}\",\"id\":\"${UUID}\",\"net\":\"ws\",\"path\":\"/${UUID}-vmess\",\"port\":\"80\",\"ps\":\"${NODE_NAME} vmess ws\",\"scy\":\"none\",\"sni\":\"\",\"tls\":\"\",\"type\":\"\",\"v\":\"2\"}" | sed "s/Cg==$//")
$(text 52)")
EOF
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://${UUID}@${CDN}:443?security=tls&sni=${VLESS_HOST_DOMAIN}&type=ws&path=/${UUID}-vless?ed%3D2048&host=${VLESS_HOST_DOMAIN}&encryption=none#${NODE_NAME}%20vless%20ws
$(text 52)")
EOF
[ -n "$PORT_H2_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://${UUID}@${SERVER_IP_1}:${PORT_H2_REALITY}?security=reality&sni=${TLS_SERVER}&alpn=h2&fp=chrome&pbk=${REALITY_PUBLIC}&type=http&encryption=none#${NODE_NAME}%20h2-reality-vision")
EOF
[ -n "$PORT_GRPC_REALITY" ] && cat >> $WORK_DIR/list << EOF
----------------------------
$(hint "vless://${UUID}@${SERVER_IP_1}:${PORT_GRPC_REALITY}?security=reality&sni=${TLS_SERVER}&fp=chrome&pbk=${REALITY_PUBLIC}&type=grpc&serviceName=grpc&encryption=none#${NODE_NAME}%20grpc-reality-vision")
EOF
cat >> $WORK_DIR/list << EOF
*******************************************
┌────────────────┐
│ │
│ $(warning "Sing-box") │
│ │
└────────────────┘
----------------------------
$(info "{
\"outbounds\":[")
EOF
[ -n "$PORT_XTLS_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"vless\",
\"tag\": \"${NODE_NAME} xtls-reality-vision\",
\"server\":\"${SERVER_IP}\",
\"server_port\":${PORT_XTLS_REALITY},
\"uuid\":\"${UUID}\",
\"flow\":\"xtls-rprx-vision\",
\"packet_encoding\":\"xudp\",
\"tls\":{
\"enabled\":true,
\"server_name\":\"${TLS_SERVER}\",
\"utls\":{
\"enabled\":true,
\"fingerprint\":\"chrome\"
},
\"reality\":{
\"enabled\":true,
\"public_key\":\"${REALITY_PUBLIC}\",
\"short_id\":\"\"
}
},
\"multiplex\": {
\"enabled\": true,
\"protocol\": \"h2mux\",
\"max_connections\": 16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_HYSTERIA2" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"hysteria2\",
\"tag\": \"${NODE_NAME} hysteria2\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_HYSTERIA2},
\"up_mbps\": 200,
\"down_mbps\": 1000,
\"obfs\": {
\"type\": \"salamander\",
\"password\": \"${UUID}\"
},
\"password\": \"${UUID}\",
\"tls\": {
\"enabled\": true,
\"insecure\": true,
\"server_name\": \"\",
\"alpn\": [ \"h3\" ]
}
},")
EOF
[ -n "$PORT_TUIC" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"tuic\",
\"tag\": \"${NODE_NAME} tuic\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_TUIC},
\"uuid\": \"${UUID}\",
\"password\": \"${UUID}\",
\"congestion_control\": \"bbr\",
\"udp_relay_mode\": \"native\",
\"zero_rtt_handshake\": false,
\"heartbeat\": \"10s\",
\"tls\": {
\"enabled\": true,
\"insecure\": true,
\"server_name\": \"\",
\"alpn\": [ \"h3\" ]
}
},")
EOF
[ -n "$PORT_SHADOWTLS" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"shadowsocks\",
\"tag\": \"${NODE_NAME} ShadowTLS v3\",
\"method\": \"2022-blake3-aes-128-gcm\",
\"password\": \"${SHADOWTLS_PASSWORD}\",
\"detour\": \"shadowtls-out\",
\"udp_over_tcp\": false,
\"multiplex\": {
\"enabled\": true,
\"protocol\": \"h2mux\",
\"max_connections\": 16,
\"padding\": true
}
},
{
\"type\": \"shadowtls\",
\"tag\": \"shadowtls-out\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_SHADOWTLS},
\"version\": 3,
\"password\": \"${UUID}\",
\"tls\": {
\"enabled\": true,
\"server_name\": \"${TLS_SERVER}\",
\"utls\": {
\"enabled\": true,
\"fingerprint\": \"chrome\"
}
}
},")
EOF
[ -n "$PORT_SHADOWSOCKS" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"shadowsocks\",
\"tag\": \"${NODE_NAME} ss\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_SHADOWSOCKS},
\"method\": \"aes-128-gcm\",
\"password\": \"${UUID}\",
\"multiplex\": {
\"enabled\": true,
\"protocol\": \"h2mux\",
\"max_connections\": 16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_TROJAN" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"trojan\",
\"tag\": \"${NODE_NAME} trojan\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_TROJAN},
\"password\": \"${UUID}\",
\"tls\": {
\"enabled\":true,
\"insecure\": true,
\"server_name\":\"\",
\"utls\": {
\"enabled\":true,
\"fingerprint\":\"chrome\"
}
},
\"multiplex\": {
\"enabled\":true,
\"protocol\":\"h2mux\",
\"max_connections\": 16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"vmess\",
\"tag\": \"${NODE_NAME} vmess ws\",
\"server\":\"${CDN}\",
\"server_port\":80,
\"uuid\":\"${UUID}\",
\"transport\": {
\"type\":\"ws\",
\"path\":\"/${UUID}-vmess\",
\"headers\": {
\"Host\": \"${VMESS_HOST_DOMAIN}\"
},
\"max_early_data\":2408,
\"early_data_header_name\":\"Sec-WebSocket-Protocol\"
},
\"multiplex\": {
\"enabled\":true,
\"protocol\":\"h2mux\",
\"max_streams\":16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"vless\",
\"tag\": \"${NODE_NAME} vless ws\",
\"server\":\"${CDN}\",
\"server_port\":443,
\"uuid\":\"${UUID}\",
\"tls\": {
\"enabled\":true,
\"server_name\":\"${VLESS_HOST_DOMAIN}\",
\"utls\": {
\"enabled\":true,
\"fingerprint\":\"chrome\"
}
},
\"transport\": {
\"type\":\"ws\",
\"path\":\"/${UUID}-vless\",
\"headers\": {
\"Host\": \"${VLESS_HOST_DOMAIN}\"
},
\"max_early_data\":2408,
\"early_data_header_name\":\"Sec-WebSocket-Protocol\"
},
\"multiplex\": {
\"enabled\":true,
\"protocol\":\"h2mux\",
\"max_streams\":16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_H2_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"vless\",
\"tag\": \"${NODE_NAME} h2-reality-vision\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_H2_REALITY},
\"uuid\":\"${UUID}\",
\"tls\": {
\"enabled\":true,
\"server_name\":\"${TLS_SERVER}\",
\"utls\": {
\"enabled\":true,
\"fingerprint\":\"chrome\"
},
\"reality\":{
\"enabled\":true,
\"public_key\":\"${REALITY_PUBLIC}\",
\"short_id\":\"\"
}
},
\"packet_encoding\": \"xudp\",
\"transport\": {
\"type\": \"http\"
},
\"multiplex\": {
\"enabled\":true,
\"protocol\":\"h2mux\",
\"max_streams\":16,
\"padding\": true
}
},")
EOF
[ -n "$PORT_GRPC_REALITY" ] && cat >> $WORK_DIR/list << EOF
$(info " {
\"type\": \"vless\",
\"tag\": \"${NODE_NAME} grpc-reality-vision\",
\"server\": \"${SERVER_IP}\",
\"server_port\": ${PORT_GRPC_REALITY},
\"uuid\":\"${UUID}\",
\"tls\": {
\"enabled\":true,
\"server_name\":\"${TLS_SERVER}\",
\"utls\": {
\"enabled\":true,
\"fingerprint\":\"chrome\"
},
\"reality\":{
\"enabled\":true,
\"public_key\":\"${REALITY_PUBLIC}\",
\"short_id\":\"\"
}
},
\"packet_encoding\": \"xudp\",
\"transport\": {
\"type\": \"grpc\",
\"service_name\": \"grpc\"
}
},")
EOF
sed -i '$s/},/}/' $WORK_DIR/list
cat >> $WORK_DIR/list << EOF
$(info " ]
}
$(text 72)")
EOF
[ -n "$PORT_VMESS_WS" ] && TYPE_HOST_DOMAIN=$VMESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VMESS_WS && hint "\n $(text 52)" >> $WORK_DIR/list
[ -n "$PORT_VLESS_WS" ] && TYPE_HOST_DOMAIN=$VLESS_HOST_DOMAIN && TYPE_PORT_WS=$PORT_VLESS_WS && hint "\n $(text 52)" >> $WORK_DIR/list
# 显示节点信息
cat $WORK_DIR/list
# 显示脚本使用情况数据
info "\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
systemctl stop 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 start 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
EXISTED_CHECK=($(sed -n "/tag/s/.*\"tag\":\"\(.*\)-in.*/\1/gp" $WORK_DIR/conf/*_inbounds.json | sed "s/xtls-reality/XTLS@+@reality/; s/shadowtls/shadowTLS/; s/vmess-ws/vmess@+@ws/; s/vless-ws/vless@+@ws@+@tls/; s/h2-reality/H2@+@reality/; s/grpc-reality/gRPC@+@reality/"))
for f in "${EXISTED_CHECK[@]}"; do
[[ "$(tr 'A-Z' 'a-z' <<< "${PROTOCOL_LIST[@]// /@}")" =~ "$(tr 'A-Z' 'a-z' <<< "$f")" ]] && EXISTED_PROTOCOLS+=("$f")
done
# 查找未安装的协议,并遍历所有协议列表的数组,获取协议名后存放在 NOT_EXISTED_PROTOCOLS
for g in "${PROTOCOL_LIST[@]// /@}"; do
[[ ! "${EXISTED_CHECK[@]}" =~ "$g" ]] && NOT_EXISTED_PROTOCOLS+=("$g")
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
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
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" = [Nn] ]] && exit 0
# 把确认安装的协议遍历所有协议列表的数组,找出其下标并变为英文小写的形式
for m in "${!REINSTALL_PROTOCOLS[@]}"; do
for n in "${!PROTOCOL_LIST[@]}"; do
if [[ "$(awk -F '@' '{print $1}' <<< "${REINSTALL_PROTOCOLS[m]}")" = "$(awk -F ' ' '{print $1}' <<< "${PROTOCOL_LIST[n]}")" ]]; then
INSTALL_PROTOCOLS+=($(asc $[n+98]))
fi
done
done
systemctl stop sing-box
# 获取原有各协议的参数
if [ -s $WORK_DIR/list ]; then
SERVER_IP=$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | sed -n "s/.*{name.*server:[ ]*\([^,]\+\).*/\1/pg" | sed -n '1p')
UUID=$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | grep -m1 '{name' | sed -En 's/.*password:[ ]+[\"]*|.*uuid:[ ]+[\"]*(.*)/\1/gp' | sed "s/\([^,\"]\+\).*/\1/g")
NODE_NAME=$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | grep -m1 '{name' | sed 's/- {name:[ ]\+"//; s/[ ]\+ShadowTLS[ ]\+v3.*//; s/[ ]\+xtls-reality-vision.*//; s/[ ]\+hysteria2.*//; s/[ ]\+tuic.*//; s/[ ]\+ss.*//; s/[ ]\+trojan.*//; s/[ ]\+trojan.*//; s/[ ]\+vmess[ ]\+ws.*//; s/[ ]\+vless[ ]\+.*//')
EXISTED_PORTS=$(awk -F ':|,' '/listen_port/{print $2}' $WORK_DIR/conf/*)
START_PORT=$(awk 'NR == 1 { min = $0 } { if ($0 < min) min = $0; count++ } END {print min}' <<< "$EXISTED_PORTS")
[ -s $WORK_DIR/conf/*_VMESS_WS_inbounds.json ] && WS_SERVER_IP=$(grep -A2 "{name.*vmess[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $2}') && VMESS_HOST_DOMAIN=$(grep -A2 "{name.*vmess[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $4}') && CDN=$(sed -n "s/.*{name.*vmess[ ]\+ws.*server:[ ]\+\([^,]\+\).*/\1/gp" $WORK_DIR/list)
[ -s $WORK_DIR/conf/*_VLESS_WS_inbounds.json ] && WS_SERVER_IP=$(grep -A2 "{name.*vless[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $2}') && VLESS_HOST_DOMAIN=$(grep -A2 "{name.*vless[ ]\+ws" $WORK_DIR/list | awk -F'[][]' 'NR==3 {print $4}') && CDN=$(sed -n "s/.*{name.*vless[ ]\+ws.*server:[ ]\+\([^,]\+\).*/\1/gp" $WORK_DIR/list)
fi
# 删除不需要的协议配置文件
for o in "${REMOVE_PROTOCOLS[@]}"; do
rm -f $WORK_DIR/conf/*$(tr 'a-z' 'A-Z' <<< "$o" | awk -F '@' '{print $1}')*inbounds.json
done
# 寻找已存在协议中原有的端口号
for p in "${KEEP_PROTOCOLS[@]}"; do
KEEP_PORTS+=($(awk -F '[:,]' '/listen_port/{print $2}' $WORK_DIR/conf/*$(tr 'a-z' 'A-Z' <<< "$p" | awk -F '@' '{print $1}')*inbounds.json))
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
if [[ "${KEEP_PROTOCOLS[@]}" =~ "reality" ]]; then
REALITY_PRIVATE=$(awk -F '"' '/private_key/{print $4; exit}' $WORK_DIR/conf/*_REALITY_inbounds.json)
REALITY_PUBLIC=$(sed -n 's/.*{name.*public-key:[ ]*\([^,]\+\).*/\1/pg' $WORK_DIR/list | sed -n 1p)
else
REALITY_KEYPAIR=$($WORK_DIR/sing-box generate reality-keypair)
REALITY_PRIVATE=$(awk '/PrivateKey/{print $NF}' <<< "$REALITY_KEYPAIR")
REALITY_PUBLIC=$(awk '/PublicKey/{print $NF}' <<< "$REALITY_KEYPAIR")
fi
PORT_XTLS_REALITY=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 Hysteria2 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
PORT_HYSTERIA2=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 Tuic V5 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
PORT_TUIC=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 shadowTLS 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
[[ "${KEEP_PROTOCOLS[@]}" =~ "shadowTLS" ]] && SHADOWTLS_PASSWORD=$(sed -r "s/\x1B\[[0-9;]*[mG]//g" $WORK_DIR/list | sed -n 's/.*{name.*password:[ ]*\"\([^\"]\+\)".*shadow-tls.*/\1/pg') || SHADOWTLS_PASSWORD=$($WORK_DIR/sing-box generate rand --base64 16)
PORT_SHADOWTLS=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 Shadowsocks 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
PORT_SHADOWSOCKS=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 Trojan 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
PORT_TROJAN=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 vmess + ws 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
WS_SERVER_IP=$SERVER_IP
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
PORT_VMESS_WS=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 获取原始 vless + ws + tls 配置信息
CHECK_PROTOCOLS=$(asc "$CHECK_PROTOCOLS" ++)
if [[ "${INSTALL_PROTOCOLS[@]}" =~ "$CHECK_PROTOCOLS" ]]; then
WS_SERVER_IP=$SERVER_IP
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
PORT_VLESS_WS=${REINSTALL_PORTS[$(awk -v target=$CHECK_PROTOCOLS '{ for(i=1; i<=NF; i++) if($i == target) { print i-1; break } }' <<< "${INSTALL_PROTOCOLS[*]}")]}
fi
# 选择可以输入 cdn
input_cdn
# 生成各协议的 json 文件
sing-box_json change
systemctl start sing-box
# 再次检测状态,运行 Sing-box
check_install
check_sing-box_stats
export_list
}
# 卸载 Sing-box 全家桶
uninstall() {
if [ -d $WORK_DIR ]; then
[ "$SYSTEM" = 'Alpine' ] && systemctl stop sing-box 2>/dev/null || cmd_systemctl disable sing-box 2>/dev/null
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 系统,删除开机自启动
[ "$SYSTEM" = 'Alpine' ] && ( rm -f /etc/local.d/sing-box.start; rc-update add local >/dev/null 2>&1 )
}
# Sing-box 的最新版本
version() {
local ONLINE=$(wget --no-check-certificate -qO- "https://api.github.com/repos/SagerNet/sing-box/releases" | awk -F '["v]' '/tag_name.*beta/{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" = [Yy] ]]; then
check_system_info
wget --no-check-certificate -c ${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
systemctl stop 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 start 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() {
OPTION[0]="0 . $(text 35)"
ACTION[0]() { exit; }
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' ] && 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; 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
}
menu() {
clear
hint " $(text 2) "
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
}
statistics_of_run-times
# 传参
[[ "$*" =~ -[Ee] ]] && L=E
[[ "$*" =~ -[Cc] ]] && L=C
while getopts ":P:p:OoUuVvNnBbRr" OPTNAME; do
case "$OPTNAME" in
'P'|'p' ) START_PORT=$OPTARG; select_language; check_install; [ "$STATUS" = "$(text 26)" ] && error "\n Sing-box $(text 26) "; change_start_port; exit 0 ;;
'O'|'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' ] && 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'|'u' ) select_language; check_system_info; uninstall; exit 0 ;;
'N'|'n' ) select_language; [ ! -s $WORK_DIR/list ] && error " Sing-box $(text 26) "; export_list; exit 0 ;;
'V'|'v' ) select_language; check_arch; version; exit 0 ;;
'B'|'b' ) select_language; bash <(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master/tcp.sh"); exit ;;
'R'|'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