diff --git a/MHSanaei_3x-ui.sh b/MHSanaei_3x-ui.sh new file mode 100644 index 0000000..4c959a2 --- /dev/null +++ b/MHSanaei_3x-ui.sh @@ -0,0 +1,232 @@ +#!/bin/bash + +red='\033[0;31m' +green='\033[0;32m' +blue='\033[0;34m' +yellow='\033[0;33m' +plain='\033[0m' + +cur_dir=$(pwd) + +# check root +[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 + +# Check OS and set release variable +if [[ -f /etc/os-release ]]; then + source /etc/os-release + release=$ID +elif [[ -f /usr/lib/os-release ]]; then + source /usr/lib/os-release + release=$ID +else + echo "Failed to check the system OS, please contact the author!" >&2 + exit 1 +fi +echo "The OS release is: $release" + +arch() { + case "$(uname -m)" in + x86_64 | x64 | amd64) echo 'amd64' ;; + i*86 | x86) echo '386' ;; + armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; + armv7* | armv7 | arm) echo 'armv7' ;; + armv6* | armv6) echo 'armv6' ;; + armv5* | armv5) echo 'armv5' ;; + s390x) echo 's390x' ;; + *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; + esac +} + +echo "Arch: $(arch)" + +install_base() { + case "${release}" in + ubuntu | debian | armbian) + apt-get update && apt-get install -y -q wget curl tar tzdata + ;; + centos | rhel | almalinux | rocky | ol) + yum -y update && yum install -y -q wget curl tar tzdata + ;; + fedora | amzn | virtuozzo) + dnf -y update && dnf install -y -q wget curl tar tzdata + ;; + arch | manjaro | parch) + pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata + ;; + opensuse-tumbleweed) + zypper refresh && zypper -q install -y wget curl tar timezone + ;; + *) + apt-get update && apt-get install -y -q wget curl tar tzdata + ;; + esac +} + +gen_random_string() { + local length="$1" + local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' /dev/null | tr -d '[:space:]') + if [[ -n "${server_ip}" ]]; then + break + fi + done + + if [[ ${#existing_webBasePath} -lt 4 ]]; then + if [[ "$existing_hasDefaultCredential" == "true" ]]; then + local config_webBasePath=$(gen_random_string 18) + local config_username=$(gen_random_string 10) + local config_password=$(gen_random_string 10) + + read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm + if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then + read -rp "Please set up the panel port: " config_port + echo -e "${yellow}Your Panel Port is: ${config_port}${plain}" + else + local config_port=$(shuf -i 1024-62000 -n 1) + echo -e "${yellow}Generated random port: ${config_port}${plain}" + fi + + /usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}" + echo -e "This is a fresh installation, generating random login info for security concerns:" + echo -e "###############################################" + echo -e "${green}Username: ${config_username}${plain}" + echo -e "${green}Password: ${config_password}${plain}" + echo -e "${green}Port: ${config_port}${plain}" + echo -e "${green}WebBasePath: ${config_webBasePath}${plain}" + echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}" + echo -e "###############################################" + else + local config_webBasePath=$(gen_random_string 18) + echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}" + /usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" + echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}" + echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}" + fi + else + if [[ "$existing_hasDefaultCredential" == "true" ]]; then + local config_username=$(gen_random_string 10) + local config_password=$(gen_random_string 10) + + echo -e "${yellow}Default credentials detected. Security update required...${plain}" + /usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" + echo -e "Generated new random login credentials:" + echo -e "###############################################" + echo -e "${green}Username: ${config_username}${plain}" + echo -e "${green}Password: ${config_password}${plain}" + echo -e "###############################################" + else + echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}" + fi + fi + + /usr/local/x-ui/x-ui migrate +} + +install_x-ui() { + cd /usr/local/ + + # Download resources + if [ $# == 0 ]; then + tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$tag_version" ]]; then + echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}" + exit 1 + fi + echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." + wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz + if [[ $? -ne 0 ]]; then + echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" + exit 1 + fi + else + tag_version=$1 + tag_version_numeric=${tag_version#v} + min_version="2.3.5" + + if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then + echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}" + exit 1 + fi + + url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" + echo -e "Beginning to install x-ui $1" + wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url} + if [[ $? -ne 0 ]]; then + echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}" + exit 1 + fi + fi + wget -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh + + # Stop x-ui service and remove old resources + if [[ -e /usr/local/x-ui/ ]]; then + systemctl stop x-ui + rm /usr/local/x-ui/ -rf + fi + + # Extract resources and set permissions + tar zxvf x-ui-linux-$(arch).tar.gz + rm x-ui-linux-$(arch).tar.gz -f + + cd x-ui + chmod +x x-ui + chmod +x x-ui.sh + + # Check the system's architecture and rename the file accordingly + if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then + mv bin/xray-linux-$(arch) bin/xray-linux-arm + chmod +x bin/xray-linux-arm + fi + chmod +x x-ui bin/xray-linux-$(arch) + + # Update x-ui cli and se set permission + mv -f /usr/bin/x-ui-temp /usr/bin/x-ui + chmod +x /usr/bin/x-ui + config_after_install + + cp -f x-ui.service /etc/systemd/system/ + systemctl daemon-reload + systemctl enable x-ui + systemctl start x-ui + echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..." + echo -e "" + echo -e "┌───────────────────────────────────────────────────────┐ +│ ${blue}x-ui control menu usages (subcommands):${plain} │ +│ │ +│ ${blue}x-ui${plain} - Admin Management Script │ +│ ${blue}x-ui start${plain} - Start │ +│ ${blue}x-ui stop${plain} - Stop │ +│ ${blue}x-ui restart${plain} - Restart │ +│ ${blue}x-ui status${plain} - Current Status │ +│ ${blue}x-ui settings${plain} - Current Settings │ +│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │ +│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │ +│ ${blue}x-ui log${plain} - Check logs │ +│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │ +│ ${blue}x-ui update${plain} - Update │ +│ ${blue}x-ui legacy${plain} - legacy version │ +│ ${blue}x-ui install${plain} - Install │ +│ ${blue}x-ui uninstall${plain} - Uninstall │ +└───────────────────────────────────────────────────────┘" +} + +echo -e "${green}Running...${plain}" +install_base +install_x-ui $1 diff --git a/hysteria.sh b/hysteria.sh new file mode 100644 index 0000000..ab891b9 --- /dev/null +++ b/hysteria.sh @@ -0,0 +1,594 @@ +#!/bin/bash + +export LANG=en_US.UTF-8 + +RED="\033[31m" +GREEN="\033[32m" +YELLOW="\033[33m" +PLAIN="\033[0m" + +red(){ + echo -e "\033[31m\033[01m$1\033[0m" +} + +green(){ + echo -e "\033[32m\033[01m$1\033[0m" +} + +yellow(){ + echo -e "\033[33m\033[01m$1\033[0m" +} + +# 判断系统及定义系统安装依赖方式 +REGEX=("debian" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "'amazon linux'" "fedora") +RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Fedora") +PACKAGE_UPDATE=("apt-get update" "apt-get update" "yum -y update" "yum -y update" "yum -y update") +PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install" "yum -y install" "yum -y install") +PACKAGE_REMOVE=("apt -y remove" "apt -y remove" "yum -y remove" "yum -y remove" "yum -y remove") +PACKAGE_UNINSTALL=("apt -y autoremove" "apt -y autoremove" "yum -y autoremove" "yum -y autoremove" "yum -y autoremove") + +[[ $EUID -ne 0 ]] && red "注意: 请在root用户下运行脚本" && exit 1 + +CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(hostnamectl 2>/dev/null | grep -i system | cut -d : -f2)" "$(lsb_release -sd 2>/dev/null)" "$(grep -i description /etc/lsb-release 2>/dev/null | cut -d \" -f2)" "$(grep . /etc/redhat-release 2>/dev/null)" "$(grep . /etc/issue 2>/dev/null | cut -d \\ -f1 | sed '/^[ ]*$/d')") + +for i in "${CMD[@]}"; do + SYS="$i" && [[ -n $SYS ]] && break +done + +for ((int = 0; int < ${#REGEX[@]}; int++)); do + [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && [[ -n $SYSTEM ]] && break +done + +[[ -z $SYSTEM ]] && red "目前暂不支持你的VPS的操作系统!" && exit 1 + +if [[ -z $(type -P curl) ]]; then + if [[ ! $SYSTEM == "CentOS" ]]; then + ${PACKAGE_UPDATE[int]} + fi + ${PACKAGE_INSTALL[int]} curl +fi + +realip(){ + ip=$(curl -s4m8 ip.gs -k) || ip=$(curl -s6m8 ip.gs -k) +} + +inst_cert(){ + green "Hysteria 2 协议证书申请方式如下:" + echo "" + echo -e " ${GREEN}1.${PLAIN} 必应自签证书 ${YELLOW}(默认)${PLAIN}" + echo -e " ${GREEN}2.${PLAIN} Acme 脚本自动申请" + echo -e " ${GREEN}3.${PLAIN} 自定义证书路径" + echo "" + read -rp "请输入选项 [1-3]: " certInput + if [[ $certInput == 2 ]]; then + cert_path="/root/cert.crt" + key_path="/root/private.key" + + chmod -R 777 /root + + chmod +rw /root/cert.crt + chmod +rw /root/private.key + + if [[ -f /root/cert.crt && -f /root/private.key ]] && [[ -s /root/cert.crt && -s /root/private.key ]] && [[ -f /root/ca.log ]]; then + domain=$(cat /root/ca.log) + green "检测到原有域名:$domain 的证书,正在应用" + hy_domain=$domain + else + WARPv4Status=$(curl -s4m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2) + WARPv6Status=$(curl -s6m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2) + if [[ $WARPv4Status =~ on|plus ]] || [[ $WARPv6Status =~ on|plus ]]; then + wg-quick down wgcf >/dev/null 2>&1 + systemctl stop warp-go >/dev/null 2>&1 + realip + wg-quick up wgcf >/dev/null 2>&1 + systemctl start warp-go >/dev/null 2>&1 + else + realip + fi + + read -p "请输入需要申请证书的域名:" domain + [[ -z $domain ]] && red "未输入域名,无法执行操作!" && exit 1 + green "已输入的域名:$domain" && sleep 1 + domainIP=$(dig @8.8.8.8 +time=2 +short "$domain" 2>/dev/null) + if echo $domainIP | grep -q "network unreachable\|timed out" || [[ -z $domainIP ]]; then + domainIP=$(dig @2001:4860:4860::8888 +time=2 aaaa +short "$domain" 2>/dev/null) + fi + if echo $domainIP | grep -q "network unreachable\|timed out" || [[ -z $domainIP ]] ; then + red "未解析出 IP,请检查域名是否输入有误" + yellow "是否尝试强行匹配?" + green "1. 是,将使用强行匹配" + green "2. 否,退出脚本" + read -p "请输入选项 [1-2]:" ipChoice + if [[ $ipChoice == 1 ]]; then + yellow "将尝试强行匹配以申请域名证书" + else + red "将退出脚本" + exit 1 + fi + fi + if [[ $domainIP == $ip ]]; then + ${PACKAGE_INSTALL[int]} curl wget sudo socat openssl + if [[ $SYSTEM == "CentOS" ]]; then + ${PACKAGE_INSTALL[int]} cronie + systemctl start crond + systemctl enable crond + else + ${PACKAGE_INSTALL[int]} cron + systemctl start cron + systemctl enable cron + fi + curl https://get.acme.sh | sh -s email=$(date +%s%N | md5sum | cut -c 1-16)@gmail.com + source ~/.bashrc + bash ~/.acme.sh/acme.sh --upgrade --auto-upgrade + bash ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt + if [[ -n $(echo $ip | grep ":") ]]; then + bash ~/.acme.sh/acme.sh --issue -d ${domain} --standalone -k ec-256 --listen-v6 --insecure + else + bash ~/.acme.sh/acme.sh --issue -d ${domain} --standalone -k ec-256 --insecure + fi + bash ~/.acme.sh/acme.sh --install-cert -d ${domain} --key-file /root/private.key --fullchain-file /root/cert.crt --ecc + if [[ -f /root/cert.crt && -f /root/private.key ]] && [[ -s /root/cert.crt && -s /root/private.key ]]; then + echo $domain > /root/ca.log + sed -i '/--cron/d' /etc/crontab >/dev/null 2>&1 + echo "0 0 * * * root bash /root/.acme.sh/acme.sh --cron -f >/dev/null 2>&1" >> /etc/crontab + green "证书申请成功! 脚本申请到的证书 (cert.crt) 和私钥 (private.key) 文件已保存到 /root 文件夹下" + yellow "证书crt文件路径如下: /root/cert.crt" + yellow "私钥key文件路径如下: /root/private.key" + hy_domain=$domain + fi + else + red "当前域名解析的IP与当前VPS使用的真实IP不匹配" + green "建议如下:" + yellow "1. 请确保CloudFlare小云朵为关闭状态(仅限DNS), 其他域名解析或CDN网站设置同理" + yellow "2. 请检查DNS解析设置的IP是否为VPS的真实IP" + yellow "3. 脚本可能跟不上时代, 建议截图发布到GitHub Issues、GitLab Issues、论坛或TG群询问" + exit 1 + fi + fi + elif [[ $certInput == 3 ]]; then + read -p "请输入公钥文件 crt 的路径:" cert_path + yellow "公钥文件 crt 的路径:$cert_path " + read -p "请输入密钥文件 key 的路径:" key_path + yellow "密钥文件 key 的路径:$key_path " + read -p "请输入证书的域名:" domain + yellow "证书域名:$domain" + hy_domain=$domain + + chmod +rw $cert_path + chmod +rw $key_path + else + green "将使用必应自签证书作为 Hysteria 2 的节点证书" + + cert_path="/etc/hysteria/cert.crt" + key_path="/etc/hysteria/private.key" + openssl ecparam -genkey -name prime256v1 -out /etc/hysteria/private.key + openssl req -new -x509 -days 36500 -key /etc/hysteria/private.key -out /etc/hysteria/cert.crt -subj "/CN=www.bing.com" + chmod 777 /etc/hysteria/cert.crt + chmod 777 /etc/hysteria/private.key + hy_domain="www.bing.com" + domain="www.bing.com" + fi +} + +inst_port(){ + iptables -t nat -F PREROUTING >/dev/null 2>&1 + + read -p "设置 Hysteria 2 端口 [1-65535](回车则随机分配端口):" port + [[ -z $port ]] && port=$(shuf -i 2000-65535 -n 1) + until [[ -z $(ss -tunlp | grep -w udp | awk '{print $5}' | sed 's/.*://g' | grep -w "$port") ]]; do + if [[ -n $(ss -tunlp | grep -w udp | awk '{print $5}' | sed 's/.*://g' | grep -w "$port") ]]; then + echo -e "${RED} $port ${PLAIN} 端口已经被其他程序占用,请更换端口重试!" + read -p "设置 Hysteria 2 端口 [1-65535](回车则随机分配端口):" port + [[ -z $port ]] && port=$(shuf -i 2000-65535 -n 1) + fi + done + + yellow "将在 Hysteria 2 节点使用的端口是:$port" + inst_jump +} + +inst_jump(){ + green "Hysteria 2 端口使用模式如下:" + echo "" + echo -e " ${GREEN}1.${PLAIN} 单端口 ${YELLOW}(默认)${PLAIN}" + echo -e " ${GREEN}2.${PLAIN} 端口跳跃" + echo "" + read -rp "请输入选项 [1-2]: " jumpInput + if [[ $jumpInput == 2 ]]; then + read -p "设置范围端口的起始端口 (建议10000-65535之间):" firstport + read -p "设置一个范围端口的末尾端口 (建议10000-65535之间,一定要比上面起始端口大):" endport + if [[ $firstport -ge $endport ]]; then + until [[ $firstport -le $endport ]]; do + if [[ $firstport -ge $endport ]]; then + red "你设置的起始端口小于末尾端口,请重新输入起始和末尾端口" + read -p "设置范围端口的起始端口 (建议10000-65535之间):" firstport + read -p "设置一个范围端口的末尾端口 (建议10000-65535之间,一定要比上面起始端口大):" endport + fi + done + fi + iptables -t nat -A PREROUTING -p udp --dport $firstport:$endport -j DNAT --to-destination :$port + ip6tables -t nat -A PREROUTING -p udp --dport $firstport:$endport -j DNAT --to-destination :$port + netfilter-persistent save >/dev/null 2>&1 + else + red "将继续使用单端口模式" + fi +} + +inst_pwd(){ + read -p "设置 Hysteria 2 密码(回车跳过为随机字符):" auth_pwd + [[ -z $auth_pwd ]] && auth_pwd=$(date +%s%N | md5sum | cut -c 1-8) + yellow "使用在 Hysteria 2 节点的密码为:$auth_pwd" +} + +inst_site(){ + read -rp "请输入 Hysteria 2 的伪装网站地址 (去除https://) [回车世嘉maimai日本网站]:" proxysite + [[ -z $proxysite ]] && proxysite="maimai.sega.jp" + yellow "使用在 Hysteria 2 节点的伪装网站为:$proxysite" +} + +insthysteria(){ + warpv6=$(curl -s6m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2) + warpv4=$(curl -s4m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2) + if [[ $warpv4 =~ on|plus || $warpv6 =~ on|plus ]]; then + wg-quick down wgcf >/dev/null 2>&1 + systemctl stop warp-go >/dev/null 2>&1 + realip + systemctl start warp-go >/dev/null 2>&1 + wg-quick up wgcf >/dev/null 2>&1 + else + realip + fi + + if [[ ! ${SYSTEM} == "CentOS" ]]; then + ${PACKAGE_UPDATE} + fi + ${PACKAGE_INSTALL} curl wget sudo qrencode procps iptables-persistent netfilter-persistent + + wget -N https://raw.githubusercontent.com/Misaka-blog/hysteria-install/main/hy2/install_server.sh + bash install_server.sh + rm -f install_server.sh + + if [[ -f "/usr/local/bin/hysteria" ]]; then + green "Hysteria 2 安装成功!" + else + red "Hysteria 2 安装失败!" + exit 1 + fi + + # 询问用户 Hysteria 配置 + inst_cert + inst_port + inst_pwd + inst_site + + # 设置 Hysteria 配置文件 + cat << EOF > /etc/hysteria/config.yaml +listen: :$port + +tls: + cert: $cert_path + key: $key_path + +quic: + initStreamReceiveWindow: 16777216 + maxStreamReceiveWindow: 16777216 + initConnReceiveWindow: 33554432 + maxConnReceiveWindow: 33554432 + +auth: + type: password + password: $auth_pwd + +masquerade: + type: proxy + proxy: + url: https://$proxysite + rewriteHost: true +EOF + + # 确定最终入站端口范围 + if [[ -n $firstport ]]; then + last_port="$port,$firstport-$endport" + else + last_port=$port + fi + + # 给 IPv6 地址加中括号 + if [[ -n $(echo $ip | grep ":") ]]; then + last_ip="[$ip]" + else + last_ip=$ip + fi + + mkdir /root/hy + cat << EOF > /root/hy/hy-client.yaml +server: $last_ip:$last_port + +auth: $auth_pwd + +tls: + sni: $hy_domain + insecure: true + +quic: + initStreamReceiveWindow: 16777216 + maxStreamReceiveWindow: 16777216 + initConnReceiveWindow: 33554432 + maxConnReceiveWindow: 33554432 + +fastOpen: true + +socks5: + listen: 127.0.0.1:5080 + +transport: + udp: + hopInterval: 30s +EOF + cat << EOF > /root/hy/hy-client.json +{ + "server": "$last_ip:$last_port", + "auth": "$auth_pwd", + "tls": { + "sni": "$hy_domain", + "insecure": true + }, + "quic": { + "initStreamReceiveWindow": 16777216, + "maxStreamReceiveWindow": 16777216, + "initConnReceiveWindow": 33554432, + "maxConnReceiveWindow": 33554432 + }, + "fastOpen": true, + "socks5": { + "listen": "127.0.0.1:5080" + }, + "transport": { + "udp": { + "hopInterval": "30s" + } + } +} +EOF + cat < /root/hy/clash-meta.yaml +mixed-port: 7890 +external-controller: 127.0.0.1:9090 +allow-lan: false +mode: rule +log-level: debug +ipv6: true +dns: + enable: true + listen: 0.0.0.0:53 + enhanced-mode: fake-ip + nameserver: + - 8.8.8.8 + - 1.1.1.1 + - 114.114.114.114 +proxies: + - name: Misaka-Hysteria2 + type: hysteria2 + server: $last_ip + port: $port + password: $auth_pwd + sni: $hy_domain + skip-cert-verify: true +proxy-groups: + - name: Proxy + type: select + proxies: + - Misaka-Hysteria2 + +rules: + - GEOIP,CN,DIRECT + - MATCH,Proxy +EOF + url="hysteria2://$auth_pwd@$last_ip:$last_port/?insecure=1&sni=$hy_domain#Misaka-Hysteria2" + echo $url > /root/hy/url.txt + nohopurl="hysteria2://$auth_pwd@$last_ip:$port/?insecure=1&sni=$hy_domain#Misaka-Hysteria2" + echo $nohopurl > /root/hy/url-nohop.txt + + systemctl daemon-reload + systemctl enable hysteria-server + systemctl start hysteria-server + if [[ -n $(systemctl status hysteria-server 2>/dev/null | grep -w active) && -f '/etc/hysteria/config.yaml' ]]; then + green "Hysteria 2 服务启动成功" + else + red "Hysteria 2 服务启动失败,请运行 systemctl status hysteria-server 查看服务状态并反馈,脚本退出" && exit 1 + fi + red "======================================================================================" + green "Hysteria 2 代理服务安装完成" + yellow "Hysteria 2 客户端 YAML 配置文件 hy-client.yaml 内容如下,并保存到 /root/hy/hy-client.yaml" + red "$(cat /root/hy/hy-client.yaml)" + yellow "Hysteria 2 客户端 JSON 配置文件 hy-client.json 内容如下,并保存到 /root/hy/hy-client.json" + red "$(cat /root/hy/hy-client.json)" + yellow "Clash Meta 客户端配置文件已保存到 /root/hy/clash-meta.yaml" + yellow "Hysteria 2 节点分享链接如下,并保存到 /root/hy/url.txt" + red "$(cat /root/hy/url.txt)" + yellow "Hysteria 2 节点单端口的分享链接如下,并保存到 /root/hy/url.txt" + red "$(cat /root/hy/url-nohop.txt)" +} + +unsthysteria(){ + systemctl stop hysteria-server.service >/dev/null 2>&1 + systemctl disable hysteria-server.service >/dev/null 2>&1 + rm -f /lib/systemd/system/hysteria-server.service /lib/systemd/system/hysteria-server@.service + rm -rf /usr/local/bin/hysteria /etc/hysteria /root/hy /root/hysteria.sh + iptables -t nat -F PREROUTING >/dev/null 2>&1 + netfilter-persistent save >/dev/null 2>&1 + + green "Hysteria 2 已彻底卸载完成!" +} + +starthysteria(){ + systemctl start hysteria-server + systemctl enable hysteria-server >/dev/null 2>&1 +} + +stophysteria(){ + systemctl stop hysteria-server + systemctl disable hysteria-server >/dev/null 2>&1 +} + +hysteriaswitch(){ + yellow "请选择你需要的操作:" + echo "" + echo -e " ${GREEN}1.${PLAIN} 启动 Hysteria 2" + echo -e " ${GREEN}2.${PLAIN} 关闭 Hysteria 2" + echo -e " ${GREEN}3.${PLAIN} 重启 Hysteria 2" + echo "" + read -rp "请输入选项 [0-3]: " switchInput + case $switchInput in + 1 ) starthysteria ;; + 2 ) stophysteria ;; + 3 ) stophysteria && starthysteria ;; + * ) exit 1 ;; + esac +} + +changeport(){ + oldport=$(cat /etc/hysteria/config.yaml 2>/dev/null | sed -n 1p | awk '{print $2}' | awk -F ":" '{print $2}') + + read -p "设置 Hysteria 2 端口[1-65535](回车则随机分配端口):" port + [[ -z $port ]] && port=$(shuf -i 2000-65535 -n 1) + + until [[ -z $(ss -tunlp | grep -w udp | awk '{print $5}' | sed 's/.*://g' | grep -w "$port") ]]; do + if [[ -n $(ss -tunlp | grep -w udp | awk '{print $5}' | sed 's/.*://g' | grep -w "$port") ]]; then + echo -e "${RED} $port ${PLAIN} 端口已经被其他程序占用,请更换端口重试!" + read -p "设置 Hysteria 2 端口 [1-65535](回车则随机分配端口):" port + [[ -z $port ]] && port=$(shuf -i 2000-65535 -n 1) + fi + done + + sed -i "1s#$oldport#$port#g" /etc/hysteria/config.yaml + sed -i "1s#$oldport#$port#g" /root/hy/hy-client.yaml + sed -i "2s#$oldport#$port#g" /root/hy/hy-client.json + + stophysteria && starthysteria + + green "Hysteria 2 端口已成功修改为:$port" + yellow "请手动更新客户端配置文件以使用节点" + showconf +} + +changepasswd(){ + oldpasswd=$(cat /etc/hysteria/config.yaml 2>/dev/null | sed -n 15p | awk '{print $2}') + + read -p "设置 Hysteria 2 密码(回车跳过为随机字符):" passwd + [[ -z $passwd ]] && passwd=$(date +%s%N | md5sum | cut -c 1-8) + + sed -i "1s#$oldpasswd#$passwd#g" /etc/hysteria/config.yaml + sed -i "1s#$oldpasswd#$passwd#g" /root/hy/hy-client.yaml + sed -i "3s#$oldpasswd#$passwd#g" /root/hy/hy-client.json + + stophysteria && starthysteria + + green "Hysteria 2 节点密码已成功修改为:$passwd" + yellow "请手动更新客户端配置文件以使用节点" + showconf +} + +change_cert(){ + old_cert=$(cat /etc/hysteria/config.yaml | grep cert | awk -F " " '{print $2}') + old_key=$(cat /etc/hysteria/config.yaml | grep key | awk -F " " '{print $2}') + old_hydomain=$(cat /root/hy/hy-client.yaml | grep sni | awk '{print $2}') + + inst_cert + + sed -i "s!$old_cert!$cert_path!g" /etc/hysteria/config.yaml + sed -i "s!$old_key!$key_path!g" /etc/hysteria/config.yaml + sed -i "6s/$old_hydomain/$hy_domain/g" /root/hy/hy-client.yaml + sed -i "5s/$old_hydomain/$hy_domain/g" /root/hy/hy-client.json + + stophysteria && starthysteria + + green "Hysteria 2 节点证书类型已成功修改" + yellow "请手动更新客户端配置文件以使用节点" + showconf +} + +changeproxysite(){ + oldproxysite=$(cat /etc/hysteria/config.yaml | grep url | awk -F " " '{print $2}' | awk -F "https://" '{print $2}') + + inst_site + + sed -i "s#$oldproxysite#$proxysite#g" /etc/caddy/Caddyfile + + stophysteria && starthysteria + + green "Hysteria 2 节点伪装网站已成功修改为:$proxysite" +} + +changeconf(){ + green "Hysteria 2 配置变更选择如下:" + echo -e " ${GREEN}1.${PLAIN} 修改端口" + echo -e " ${GREEN}2.${PLAIN} 修改密码" + echo -e " ${GREEN}3.${PLAIN} 修改证书类型" + echo -e " ${GREEN}4.${PLAIN} 修改伪装网站" + echo "" + read -p " 请选择操作 [1-4]:" confAnswer + case $confAnswer in + 1 ) changeport ;; + 2 ) changepasswd ;; + 3 ) change_cert ;; + 4 ) changeproxysite ;; + * ) exit 1 ;; + esac +} + +showconf(){ + yellow "Hysteria 2 客户端 YAML 配置文件 hy-client.yaml 内容如下,并保存到 /root/hy/hy-client.yaml" + red "$(cat /root/hy/hy-client.yaml)" + yellow "Hysteria 2 客户端 JSON 配置文件 hy-client.json 内容如下,并保存到 /root/hy/hy-client.json" + red "$(cat /root/hy/hy-client.json)" + yellow "Clash Meta 客户端配置文件已保存到 /root/hy/clash-meta.yaml" + yellow "Hysteria 2 节点分享链接如下,并保存到 /root/hy/url.txt" + red "$(cat /root/hy/url.txt)" + yellow "Hysteria 2 节点单端口的分享链接如下,并保存到 /root/hy/url.txt" + red "$(cat /root/hy/url-nohop.txt)" +} + +update_core(){ + wget -N https://raw.githubusercontent.com/Misaka-blog/hysteria-install/main/hy2/install_server.sh + bash install_server.sh + + rm -f install_server.sh +} + +menu() { + clear + echo "#############################################################" + echo -e "# ${RED}Hysteria 2 一键安装脚本${PLAIN} #" + echo -e "# ${GREEN}作者${PLAIN}: MisakaNo の 小破站 #" + echo -e "# ${GREEN}博客${PLAIN}: https://blog.misaka.cyou #" + echo -e "# ${GREEN}GitHub 项目${PLAIN}: https://github.com/Misaka-blog #" + echo -e "# ${GREEN}GitLab 项目${PLAIN}: https://gitlab.com/Misaka-blog #" + echo -e "# ${GREEN}Telegram 频道${PLAIN}: https://t.me/misakanocchannel #" + echo -e "# ${GREEN}Telegram 群组${PLAIN}: https://t.me/misakanoc #" + echo -e "# ${GREEN}YouTube 频道${PLAIN}: https://www.youtube.com/@misaka-blog #" + echo "#############################################################" + echo "" + echo -e " ${GREEN}1.${PLAIN} 安装 Hysteria 2" + echo -e " ${GREEN}2.${PLAIN} ${RED}卸载 Hysteria 2${PLAIN}" + echo " -------------" + echo -e " ${GREEN}3.${PLAIN} 关闭、开启、重启 Hysteria 2" + echo -e " ${GREEN}4.${PLAIN} 修改 Hysteria 2 配置" + echo -e " ${GREEN}5.${PLAIN} 显示 Hysteria 2 配置文件" + echo " -------------" + echo -e " ${GREEN}6.${PLAIN} 更新 Hysteria 2 内核" + echo " -------------" + echo -e " ${GREEN}0.${PLAIN} 退出脚本" + echo "" + read -rp "请输入选项 [0-5]: " menuInput + case $menuInput in + 1 ) insthysteria ;; + 2 ) unsthysteria ;; + 3 ) hysteriaswitch ;; + 4 ) changeconf ;; + 5 ) showconf ;; + 6 ) update_core ;; + * ) exit 1 ;; + esac +} + +menu diff --git a/install_CN.sh b/install_CN.sh new file mode 100644 index 0000000..7325dee --- /dev/null +++ b/install_CN.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +cur_dir=$(pwd) + +# check root +[[ $EUID -ne 0 ]] && echo -e "${red}错误:${plain} 必须使用root用户运行此脚本!\n" && exit 1 + +# check os +if [[ -f /etc/redhat-release ]]; then + release="centos" +elif cat /etc/issue | grep -Eqi "debian"; then + release="debian" +elif cat /etc/issue | grep -Eqi "ubuntu"; then + release="ubuntu" +elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then + release="centos" +elif cat /proc/version | grep -Eqi "debian"; then + release="debian" +elif cat /proc/version | grep -Eqi "ubuntu"; then + release="ubuntu" +elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then + release="centos" +else + echo -e "${red}未检测到系统版本,请联系脚本作者!${plain}\n" && exit 1 +fi + +arch=$(arch) + +if [[ $arch == "i386" || $arch == "i686" ]]; then + arch="386" +elif [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then + arch="amd64" +elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then + arch="arm64" +elif [[ $arch == "s390x" ]]; then + arch="s390x" +else + arch="amd64" + echo -e "${red}检测架构失败,使用默认架构: ${arch}${plain}" +fi + +echo "架构: ${arch}" + +if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then + echo "本软件不支持 32 位系统(x86),请使用 64 位系统(x86_64),如果检测有误,请联系作者" + exit -1 +fi + +os_version="" + +# os version +if [[ -f /etc/os-release ]]; then + os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release) +fi +if [[ -z "$os_version" && -f /etc/lsb-release ]]; then + os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release) +fi + +if [[ x"${release}" == x"centos" ]]; then + if [[ ${os_version} -le 6 ]]; then + echo -e "${red}请使用 CentOS 7 或更高版本的系统!${plain}\n" && exit 1 + fi +elif [[ x"${release}" == x"ubuntu" ]]; then + if [[ ${os_version} -lt 16 ]]; then + echo -e "${red}请使用 Ubuntu 16 或更高版本的系统!${plain}\n" && exit 1 + fi +elif [[ x"${release}" == x"debian" ]]; then + if [[ ${os_version} -lt 8 ]]; then + echo -e "${red}请使用 Debian 8 或更高版本的系统!${plain}\n" && exit 1 + fi +fi + +install_base() { + if [[ x"${release}" == x"centos" ]]; then + yum install wget curl tar -y + else + apt-get update + apt install wget curl tar -y + fi +} + +#This function will be called when user installed x-ui out of sercurity +config_after_install() { + echo -e "${yellow}出于安全考虑,安装/更新完成后需要强制修改端口与账户密码${plain}" + read -p "确认是否继续? [y/n]: " config_confirm + if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then + read -p "请设置您的账户名(如未填写则随机8位字符): " config_account + [[ -z $config_account ]] && config_account=$(date +%s%N | md5sum | cut -c 1-8) + echo -e "${yellow}您的账户名将设定为:${config_account}${plain}" + read -p "请设置您的账户密码(如未填写则随机8位字符): " config_password + [[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8) + echo -e "${yellow}您的账户密码将设定为:${config_password}${plain}" + read -p "请设置面板访问端口(如未填写则随机端口号): " config_port + [[ -z $config_port ]] && config_port=$(shuf -i 2000-65535 -n 1) + until [[ -z $(ss -ntlp | awk '{print $4}' | sed 's/.*://g' | grep -w "$port") ]]; do + if [[ -n $(ss -ntlp | awk '{print $4}' | sed 's/.*://g' | grep -w "$port") ]]; then + echo -e "${red} $config_port ${plain} 端口已经其他程序占用,请更换面板端口号" + read -p "请设置面板访问端口(如未填写则随机端口号): " config_port + [[ -z $config_port ]] && config_port=$(shuf -i 2000-65535 -n 1) + fi + done + echo -e "${yellow}您的面板访问端口将设定为:${config_port}${plain}" + echo -e "${yellow}确认设定,设定中${plain}" + /usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} + echo -e "${yellow}账户密码设定完成${plain}" + /usr/local/x-ui/x-ui setting -port ${config_port} + echo -e "${yellow}面板端口设定完成${plain}" + else + config_port=$(/usr/local/x-ui/x-ui setting -show | sed -n 4p | awk -F ": " '{print $2}') + echo -e "${red}已取消, 所有设置项均为默认设置, 请及时修改${plain}" + fi +} + +install_x-ui() { + systemctl stop x-ui + cd /usr/local/ + + if [ $# == 0 ]; then + last_version=$(curl -Ls "https://api.github.com/repos/sing-web/x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$last_version" ]]; then + echo -e "${red}检测 x-ui 版本失败,可能是超出 Github API 限制,请稍后再试,或手动指定 x-ui 版本安装${plain}" + exit 1 + fi + echo -e "检测到 x-ui 最新版本:${last_version},开始安装" + wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/sing-web/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz + if [[ $? -ne 0 ]]; then + echo -e "${red}下载 x-ui 失败,请确保你的服务器能够下载 Github 的文件${plain}" + exit 1 + fi + else + last_version=$1 + url="https://github.com/sing-web/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz" + echo -e "开始安装 x-ui $1" + wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url} + if [[ $? -ne 0 ]]; then + echo -e "${red}下载 x-ui $1 失败,请确保此版本存在${plain}" + exit 1 + fi + fi + + if [[ -e /usr/local/x-ui/ ]]; then + rm /usr/local/x-ui/ -rf + fi + + tar zxvf x-ui-linux-${arch}.tar.gz + rm x-ui-linux-${arch}.tar.gz -f + cd x-ui + chmod +x x-ui bin/xray-linux-${arch} + cp -f x-ui.service /etc/systemd/system/ + wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/sing-web/x-ui/main/x-ui_CN.sh + chmod +x /usr/local/x-ui/x-ui.sh + chmod +x /usr/bin/x-ui + config_after_install + #echo -e "如果是全新安装,默认网页端口为 ${green}54321${plain},用户名和密码默认都是 ${green}admin${plain}" + #echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保 54321 端口已放行${plain}" + # echo -e "若想将 54321 修改为其它端口,输入 x-ui 命令进行修改,同样也要确保你修改的端口也是放行的" + #echo -e "" + #echo -e "如果是更新面板,则按你之前的方式访问面板" + #echo -e "" + systemctl daemon-reload + systemctl enable x-ui + systemctl start x-ui + + systemctl stop warp-go >/dev/null 2>&1 + wg-quick down wgcf >/dev/null 2>&1 + ipv4=$(curl -s4m8 ip.p3terx.com -k | sed -n 1p) + ipv6=$(curl -s6m8 ip.p3terx.com -k | sed -n 1p) + systemctl start warp-go >/dev/null 2>&1 + wg-quick up wgcf >/dev/null 2>&1 + echo -e "${green}x-ui ${last_version}${plain} 安装完成,面板已启动" + echo -e "" + echo -e "x-ui 管理脚本使用方法: " + echo -e "----------------------------------------------" + echo -e "x-ui - 显示管理菜单 (功能更多)" + echo -e "x-ui start - 启动 x-ui 面板" + echo -e "x-ui stop - 停止 x-ui 面板" + echo -e "x-ui restart - 重启 x-ui 面板" + echo -e "x-ui status - 查看 x-ui 状态" + echo -e "x-ui enable - 设置 x-ui 开机自启" + echo -e "x-ui disable - 取消 x-ui 开机自启" + echo -e "x-ui log - 查看 x-ui 日志" + echo -e "x-ui update - 更新 x-ui 面板" + echo -e "x-ui install - 安装 x-ui 面板" + echo -e "x-ui uninstall - 卸载 x-ui 面板" + echo -e "----------------------------------------------" + echo "" + if [[ -n $ipv4 ]]; then + echo -e "${yellow}面板IPv4访问地址为:${plain} ${green}http://$ipv4:$config_port ${plain}" + fi + if [[ -n $ipv6 ]]; then + echo -e "${yellow}面板IPv6访问地址为:${plain} ${green}http://[$ipv6]:$config_port ${plain}" + fi + echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保${plain} ${red} $config_port ${plain} ${yellow}端口已放行${plain}" +} + +echo -e "${green}开始安装${plain}" +install_base +install_x-ui $1 \ No newline at end of file diff --git a/mt_setup.sh b/mt_setup.sh new file mode 100644 index 0000000..cb53fba --- /dev/null +++ b/mt_setup.sh @@ -0,0 +1,417 @@ +#!/bin/bash +# MTProto一键安装脚本 +# Author: palm + +RED="\033[31m" # Error message +GREEN="\033[32m" # Success message +YELLOW="\033[33m" # Warning message +BLUE="\033[36m" # Info message +PLAIN='\033[0m' + +export MTG_CONFIG="${MTG_CONFIG:-$HOME/.config/mtg}" +export MTG_ENV="$MTG_CONFIG/env" +export MTG_SECRET="$MTG_CONFIG/secret" +export MTG_CONTAINER="${MTG_CONTAINER:-mtg}" +export MTG_IMAGENAME="${MTG_IMAGENAME:-nineseconds/mtg:1}" + +DOCKER_CMD="$(command -v docker)" +OSNAME=`hostnamectl | grep -i system | cut -d: -f2` + +IP=`curl -sL -4 ip.sb` + +colorEcho() { + echo -e "${1}${@:2}${PLAIN}" +} + +checkSystem() { + result=$(id | awk '{print $1}') + if [[ $result != "uid=0(root)" ]]; then + colorEcho $RED " 请以root身份执行该脚本" + exit 1 + fi + + res=`which yum` + if [[ "$?" != "0" ]]; then + res=`which apt` + if [ "$?" != "0" ]; then + colorEcho $RED " 不受支持的Linux系统" + exit 1 + fi + res=`hostnamectl | grep -i ubuntu` + if [[ "${res}" != "" ]]; then + OS="ubuntu" + else + OS="debian" + fi + PMT="apt" + CMD_INSTALL="apt install -y " + CMD_REMOVE="apt remove -y " + else + OS="centos" + PMT="yum" + CMD_INSTALL="yum install -y " + CMD_REMOVE="yum remove -y " + fi + res=`which systemctl` + if [[ "$?" != "0" ]]; then + colorEcho $RED " 系统版本过低,请升级到最新版本" + exit 1 + fi +} + +status() { + if [[ "$DOCKER_CMD" = "" ]]; then + echo 0 + return + elif [[ ! -f $MTG_ENV ]]; then + echo 1 + return + fi + port=`grep MTG_PORT $MTG_ENV|cut -d= -f2` + if [[ -z "$port" ]]; then + echo 2 + return + fi + res=`ss -ntlp| grep ${port} | grep docker` + if [[ -z "$res" ]]; then + echo 3 + else + echo 4 + fi +} + +statusText() { + res=`status` + case $res in + 3) + echo -e ${GREEN}已安装${PLAIN} ${RED}未运行${PLAIN} + ;; + 4) + echo -e ${GREEN}已安装${PLAIN} ${GREEN}正在运行${PLAIN} + ;; + *) + echo -e ${RED}未安装${PLAIN} + ;; + esac +} + +getData() { + read -p " 请输入MTProto端口[100-65535的一个数字]:" PORT + [[ -z "${PORT}" ]] && { + echo -e " ${RED}请输入MTProto端口!${PLAIN}" + exit 1 + } + if [[ "${PORT:0:1}" = "0" ]]; then + echo -e " ${RED}端口不能以0开头${PLAIN}" + exit 1 + fi + MTG_PORT=$PORT + mkdir -p $MTG_CONFIG + echo "MTG_IMAGENAME=$MTG_IMAGENAME" > "$MTG_ENV" + echo "MTG_PORT=$MTG_PORT" >> "$MTG_ENV" + echo "MTG_CONTAINER=$MTG_CONTAINER" >> "$MTG_ENV" +} + +installDocker() { + if [[ "$DOCKER_CMD" != "" ]]; then + systemctl enable docker + systemctl start docker + selinux + return + fi + + #$CMD_REMOVE docker docker-engine docker.io containerd runc + $PMT clean all + $CMD_INSTALL wget curl + if [[ $PMT = "apt" ]]; then + apt clean all + apt-get -y install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg-agent \ + software-properties-common + curl -fsSL https://download.docker.com/linux/$OS/gpg | apt-key add - + add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/$OS \ + $(lsb_release -cs) \ + stable" + apt update + else + wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo + yum clean all + fi + $CMD_INSTALL docker-ce docker-ce-cli containerd.io + + DOCKER_CMD="$(command -v docker)" + if [[ "$DOCKER_CMD" = "" ]]; then + echo -e " ${RED}$OSNAME docker安装失败,请到https://tizi.blog反馈${PLAIN}" + exit 1 + fi + systemctl enable docker + systemctl start docker + + selinux +} + +pullImage() { + if [[ "$DOCKER_CMD" = "" ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + exit 1 + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD pull "$MTG_IMAGENAME" > /dev/null +} + +selinux() { + if [[ -s /etc/selinux/config ]] && grep 'SELINUX=enforcing' /etc/selinux/config; then + sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config + setenforce 0 + fi +} + +firewall() { + port=$1 + systemctl status firewalld > /dev/null 2>&1 + if [[ $? -eq 0 ]];then + firewall-cmd --permanent --add-port=$port/tcp + firewall-cmd --reload + else + nl=`iptables -nL | nl | grep FORWARD | awk '{print $1}'` + if [ "$nl" != "3" ]; then + iptables -I INPUT -p tcp --dport=$port -j ACCEPT + else + res=`ufw status | grep -i inactive` + if [ "$res" = "" ]; then + ufw allow $port/tcp + fi + fi + fi +} + +start() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + if [[ ! -f "$MTG_SECRET" ]]; then + $DOCKER_CMD run \ + --rm \ + "$MTG_IMAGENAME" \ + generate-secret tls -c "$(openssl rand -hex 16).com" \ + > "$MTG_SECRET" + fi + + $DOCKER_CMD ps --filter "Name=$MTG_CONTAINER" -aq | xargs -r $DOCKER_CMD rm -fv > /dev/null + $DOCKER_CMD run \ + -d \ + --restart=unless-stopped \ + --name "$MTG_CONTAINER" \ + --ulimit nofile=51200:51200 \ + -p "$MTG_PORT:3128" \ + "$MTG_IMAGENAME" run "$(cat "$MTG_SECRET")" > /dev/null + + sleep 3 + res=`ss -ntlp| grep ${MTG_PORT} | grep docker` + if [[ "$res" = "" ]]; then + docker logs $MTG_CONTAINER | tail + echo -e " ${RED}$OSNAME 启动docker镜像失败,请到 https://tizi.blog 反馈${PLAIN}" + exit 1 + else + colorEcho $BLUE " MTProto启动成功!" + fi +} + +stop() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD stop $MTG_CONTAINER >> /dev/null + colorEcho $BLUE " MTProto停止成功!" +} + +showInfo() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + SECRET=$(cat "$MTG_SECRET") + set -a + source "$MTG_ENV" + set +a + + echo + echo -e " ${RED}MTProto代理信息:${PLAIN}" + echo + echo -n -e " ${BLUE}当前状态:${PLAIN}" + statusText + echo -e " ${BLUE}IP:${PLAIN}${RED}$IP${PLAIN}" + echo -e " ${BLUE}端口:${PLAIN}${RED}$MTG_PORT${PLAIN}" + echo -e " ${BLUE}密钥:${PLAIN}${RED}$SECRET${PLAIN}" + echo "" + echo -e " 如需获取tg://proxy形式的链接,请打开telegrame关注${GREEN}@MTProxybot${PLAIN}生成" + echo "" +} + +install() { + getData + installDocker + pullImage + start + firewall $MTG_PORT + showInfo +} + +update() { + res=`status` + if [[ $res -lt 2 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + pullImage + stop + start + showInfo +} + +uninstall() { + echo "" + read -p " 确定卸载MTProto?[y/n]:" answer + if [[ "$answer" = "y" ]] || [[ "$answer" = "Y" ]]; then + stop + rm -rf $MTG_CONFIG + docker system prune -af + systemctl stop docker + systemctl disable docker + $CMD_REMOVE docker-ce docker-ce-cli containerd.io + colorEcho $GREEN " 卸载成功" + fi +} + +restart() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + stop + start +} + +reconfig() +{ + res=`status` + if [[ $res -lt 2 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + getData + stop + start + firewall $MTG_PORT + showInfo +} + +showLog() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD logs $MTG_CONTAINER | tail +} + +menu() { + clear + echo "#############################################################" + echo -e "# ${RED}MTProto一键安装脚本${PLAIN} #" + echo "#############################################################" + echo "" + + echo -e " ${GREEN}1.${PLAIN} 安装MTProto代理" + echo -e " ${GREEN}2.${PLAIN} 更新MTProto代理" + echo -e " ${GREEN}3.${PLAIN} 卸载MTProto代理" + echo " -------------" + echo -e " ${GREEN}4.${PLAIN} 启动MTProto代理" + echo -e " ${GREEN}5.${PLAIN} 重启MTProto代理" + echo -e " ${GREEN}6.${PLAIN} 停止MTProto代理" + echo " -------------" + echo -e " ${GREEN}7.${PLAIN} 查看MTProto信息" + echo -e " ${GREEN}8.${PLAIN} 修改MTProto配置" + echo -e " ${GREEN}9.${PLAIN} 查看MTProto日志" + echo " -------------" + echo -e " ${GREEN}0.${PLAIN} 退出" + echo + echo -n " 当前状态:" + statusText + echo + + read -p " 请选择操作[0-9]:" answer + case $answer in + 0) + exit 0 + ;; + 1) + install + ;; + 2) + update + ;; + 3) + uninstall + ;; + 4) + start + ;; + 5) + restart + ;; + 6) + stop + ;; + 7) + showInfo + ;; + 8) + reconfig + ;; + 9) + showLog + ;; + *) + echo -e " ${RED}请选择正确的操作!${PLAIN}" + exit 1 + ;; + esac +} + +checkSystem + +menu diff --git a/mt_setup.sh.1 b/mt_setup.sh.1 new file mode 100644 index 0000000..cb53fba --- /dev/null +++ b/mt_setup.sh.1 @@ -0,0 +1,417 @@ +#!/bin/bash +# MTProto一键安装脚本 +# Author: palm + +RED="\033[31m" # Error message +GREEN="\033[32m" # Success message +YELLOW="\033[33m" # Warning message +BLUE="\033[36m" # Info message +PLAIN='\033[0m' + +export MTG_CONFIG="${MTG_CONFIG:-$HOME/.config/mtg}" +export MTG_ENV="$MTG_CONFIG/env" +export MTG_SECRET="$MTG_CONFIG/secret" +export MTG_CONTAINER="${MTG_CONTAINER:-mtg}" +export MTG_IMAGENAME="${MTG_IMAGENAME:-nineseconds/mtg:1}" + +DOCKER_CMD="$(command -v docker)" +OSNAME=`hostnamectl | grep -i system | cut -d: -f2` + +IP=`curl -sL -4 ip.sb` + +colorEcho() { + echo -e "${1}${@:2}${PLAIN}" +} + +checkSystem() { + result=$(id | awk '{print $1}') + if [[ $result != "uid=0(root)" ]]; then + colorEcho $RED " 请以root身份执行该脚本" + exit 1 + fi + + res=`which yum` + if [[ "$?" != "0" ]]; then + res=`which apt` + if [ "$?" != "0" ]; then + colorEcho $RED " 不受支持的Linux系统" + exit 1 + fi + res=`hostnamectl | grep -i ubuntu` + if [[ "${res}" != "" ]]; then + OS="ubuntu" + else + OS="debian" + fi + PMT="apt" + CMD_INSTALL="apt install -y " + CMD_REMOVE="apt remove -y " + else + OS="centos" + PMT="yum" + CMD_INSTALL="yum install -y " + CMD_REMOVE="yum remove -y " + fi + res=`which systemctl` + if [[ "$?" != "0" ]]; then + colorEcho $RED " 系统版本过低,请升级到最新版本" + exit 1 + fi +} + +status() { + if [[ "$DOCKER_CMD" = "" ]]; then + echo 0 + return + elif [[ ! -f $MTG_ENV ]]; then + echo 1 + return + fi + port=`grep MTG_PORT $MTG_ENV|cut -d= -f2` + if [[ -z "$port" ]]; then + echo 2 + return + fi + res=`ss -ntlp| grep ${port} | grep docker` + if [[ -z "$res" ]]; then + echo 3 + else + echo 4 + fi +} + +statusText() { + res=`status` + case $res in + 3) + echo -e ${GREEN}已安装${PLAIN} ${RED}未运行${PLAIN} + ;; + 4) + echo -e ${GREEN}已安装${PLAIN} ${GREEN}正在运行${PLAIN} + ;; + *) + echo -e ${RED}未安装${PLAIN} + ;; + esac +} + +getData() { + read -p " 请输入MTProto端口[100-65535的一个数字]:" PORT + [[ -z "${PORT}" ]] && { + echo -e " ${RED}请输入MTProto端口!${PLAIN}" + exit 1 + } + if [[ "${PORT:0:1}" = "0" ]]; then + echo -e " ${RED}端口不能以0开头${PLAIN}" + exit 1 + fi + MTG_PORT=$PORT + mkdir -p $MTG_CONFIG + echo "MTG_IMAGENAME=$MTG_IMAGENAME" > "$MTG_ENV" + echo "MTG_PORT=$MTG_PORT" >> "$MTG_ENV" + echo "MTG_CONTAINER=$MTG_CONTAINER" >> "$MTG_ENV" +} + +installDocker() { + if [[ "$DOCKER_CMD" != "" ]]; then + systemctl enable docker + systemctl start docker + selinux + return + fi + + #$CMD_REMOVE docker docker-engine docker.io containerd runc + $PMT clean all + $CMD_INSTALL wget curl + if [[ $PMT = "apt" ]]; then + apt clean all + apt-get -y install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg-agent \ + software-properties-common + curl -fsSL https://download.docker.com/linux/$OS/gpg | apt-key add - + add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/$OS \ + $(lsb_release -cs) \ + stable" + apt update + else + wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo + yum clean all + fi + $CMD_INSTALL docker-ce docker-ce-cli containerd.io + + DOCKER_CMD="$(command -v docker)" + if [[ "$DOCKER_CMD" = "" ]]; then + echo -e " ${RED}$OSNAME docker安装失败,请到https://tizi.blog反馈${PLAIN}" + exit 1 + fi + systemctl enable docker + systemctl start docker + + selinux +} + +pullImage() { + if [[ "$DOCKER_CMD" = "" ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + exit 1 + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD pull "$MTG_IMAGENAME" > /dev/null +} + +selinux() { + if [[ -s /etc/selinux/config ]] && grep 'SELINUX=enforcing' /etc/selinux/config; then + sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config + setenforce 0 + fi +} + +firewall() { + port=$1 + systemctl status firewalld > /dev/null 2>&1 + if [[ $? -eq 0 ]];then + firewall-cmd --permanent --add-port=$port/tcp + firewall-cmd --reload + else + nl=`iptables -nL | nl | grep FORWARD | awk '{print $1}'` + if [ "$nl" != "3" ]; then + iptables -I INPUT -p tcp --dport=$port -j ACCEPT + else + res=`ufw status | grep -i inactive` + if [ "$res" = "" ]; then + ufw allow $port/tcp + fi + fi + fi +} + +start() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + if [[ ! -f "$MTG_SECRET" ]]; then + $DOCKER_CMD run \ + --rm \ + "$MTG_IMAGENAME" \ + generate-secret tls -c "$(openssl rand -hex 16).com" \ + > "$MTG_SECRET" + fi + + $DOCKER_CMD ps --filter "Name=$MTG_CONTAINER" -aq | xargs -r $DOCKER_CMD rm -fv > /dev/null + $DOCKER_CMD run \ + -d \ + --restart=unless-stopped \ + --name "$MTG_CONTAINER" \ + --ulimit nofile=51200:51200 \ + -p "$MTG_PORT:3128" \ + "$MTG_IMAGENAME" run "$(cat "$MTG_SECRET")" > /dev/null + + sleep 3 + res=`ss -ntlp| grep ${MTG_PORT} | grep docker` + if [[ "$res" = "" ]]; then + docker logs $MTG_CONTAINER | tail + echo -e " ${RED}$OSNAME 启动docker镜像失败,请到 https://tizi.blog 反馈${PLAIN}" + exit 1 + else + colorEcho $BLUE " MTProto启动成功!" + fi +} + +stop() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD stop $MTG_CONTAINER >> /dev/null + colorEcho $BLUE " MTProto停止成功!" +} + +showInfo() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + SECRET=$(cat "$MTG_SECRET") + set -a + source "$MTG_ENV" + set +a + + echo + echo -e " ${RED}MTProto代理信息:${PLAIN}" + echo + echo -n -e " ${BLUE}当前状态:${PLAIN}" + statusText + echo -e " ${BLUE}IP:${PLAIN}${RED}$IP${PLAIN}" + echo -e " ${BLUE}端口:${PLAIN}${RED}$MTG_PORT${PLAIN}" + echo -e " ${BLUE}密钥:${PLAIN}${RED}$SECRET${PLAIN}" + echo "" + echo -e " 如需获取tg://proxy形式的链接,请打开telegrame关注${GREEN}@MTProxybot${PLAIN}生成" + echo "" +} + +install() { + getData + installDocker + pullImage + start + firewall $MTG_PORT + showInfo +} + +update() { + res=`status` + if [[ $res -lt 2 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + pullImage + stop + start + showInfo +} + +uninstall() { + echo "" + read -p " 确定卸载MTProto?[y/n]:" answer + if [[ "$answer" = "y" ]] || [[ "$answer" = "Y" ]]; then + stop + rm -rf $MTG_CONFIG + docker system prune -af + systemctl stop docker + systemctl disable docker + $CMD_REMOVE docker-ce docker-ce-cli containerd.io + colorEcho $GREEN " 卸载成功" + fi +} + +restart() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + stop + start +} + +reconfig() +{ + res=`status` + if [[ $res -lt 2 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + getData + stop + start + firewall $MTG_PORT + showInfo +} + +showLog() { + res=`status` + if [[ $res -lt 3 ]]; then + echo -e " ${RED}MTProto未安装,请先安装!${PLAIN}" + return + fi + + set -a + source "$MTG_ENV" + set +a + + $DOCKER_CMD logs $MTG_CONTAINER | tail +} + +menu() { + clear + echo "#############################################################" + echo -e "# ${RED}MTProto一键安装脚本${PLAIN} #" + echo "#############################################################" + echo "" + + echo -e " ${GREEN}1.${PLAIN} 安装MTProto代理" + echo -e " ${GREEN}2.${PLAIN} 更新MTProto代理" + echo -e " ${GREEN}3.${PLAIN} 卸载MTProto代理" + echo " -------------" + echo -e " ${GREEN}4.${PLAIN} 启动MTProto代理" + echo -e " ${GREEN}5.${PLAIN} 重启MTProto代理" + echo -e " ${GREEN}6.${PLAIN} 停止MTProto代理" + echo " -------------" + echo -e " ${GREEN}7.${PLAIN} 查看MTProto信息" + echo -e " ${GREEN}8.${PLAIN} 修改MTProto配置" + echo -e " ${GREEN}9.${PLAIN} 查看MTProto日志" + echo " -------------" + echo -e " ${GREEN}0.${PLAIN} 退出" + echo + echo -n " 当前状态:" + statusText + echo + + read -p " 请选择操作[0-9]:" answer + case $answer in + 0) + exit 0 + ;; + 1) + install + ;; + 2) + update + ;; + 3) + uninstall + ;; + 4) + start + ;; + 5) + restart + ;; + 6) + stop + ;; + 7) + showInfo + ;; + 8) + reconfig + ;; + 9) + showLog + ;; + *) + echo -e " ${RED}请选择正确的操作!${PLAIN}" + exit 1 + ;; + esac +} + +checkSystem + +menu diff --git a/singbox.sh b/singbox.sh new file mode 100644 index 0000000..df7826d --- /dev/null +++ b/singbox.sh @@ -0,0 +1,4981 @@ +#!/bin/bash + +RED='\033[0;31m' +CYAN='\033[0;36m' +YELLOW='\033[0;33m' +NC='\033[0m' + +disable_option=false +enable_ech=false +listen_port="" +override_port="" +ip_v4="" +ip_v6="" +record_content="" +record_type="" +record_name="" +obfs_password="" +domain="" +domain_name="" +up_mbps="" +down_mbps="" +certificate_path="" +private_key_path="" +public_key="" +private_key="" +multiplex_config="" +brutal_config="" +ech_key=() +ech_config=() +user_names=() +user_passwords=() +user_uuids=() +ss_passwords=() +stls_passwords=() +short_ids=() + +function check_firewall_configuration() { + local os_name=$(uname -s) + local firewall + if [[ $os_name == "Linux" ]]; then + if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then + firewall="ufw" + elif command -v ip6tables >/dev/null 2>&1 && ip6tables -S | grep -q "INPUT -j DROP"; then + firewall="ip6tables" + elif command -v iptables >/dev/null 2>&1 && iptables -S | grep -q "INPUT -j DROP"; then + firewall="iptables" + elif systemctl is-active --quiet netfilter-persistent; then + firewall="iptables-persistent" + elif systemctl is-active --quiet iptables.service; then + firewall="iptables-service" + elif command -v firewalld >/dev/null 2>&1 && firewall-cmd --state | grep -q "running"; then + firewall="firewalld" + fi + fi + if [[ -z $firewall ]]; then + echo "No firewall configuration detected or firewall is not enabled, skipping firewall configuration." + return + fi + echo "Checking firewall configuration..." + case $firewall in + ufw) + if ! ufw status | grep -q "Status: active" 2>/dev/null; then + ufw enable > /dev/null 2>&1 + fi + + if ! ufw status | grep -q " $listen_port" 2>/dev/null; then + ufw allow "$listen_port" > /dev/null 2>&1 + fi + + if ! ufw status | grep -q " $override_port" 2>/dev/null; then + ufw allow "$override_port" > /dev/null 2>&1 + fi + + if ! ufw status | grep -q " $fallback_port" 2>/dev/null; then + ufw allow "$fallback_port" > /dev/null 2>&1 + fi + + if ! ufw status | grep -q " 80" 2>/dev/null; then + ufw allow 80 > /dev/null 2>&1 + fi + echo "Firewall configuration has been updated." + ;; + iptables | iptables-persistent | iptables-service) + if ! iptables -C INPUT -p tcp --dport "$listen_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p tcp --dport "$listen_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p udp --dport "$listen_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p udp --dport "$listen_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p tcp --dport "$override_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p tcp --dport "$override_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p udp --dport "$override_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p udp --dport "$override_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p tcp --dport "$fallback_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p tcp --dport "$fallback_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p udp --dport "$fallback_port" -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p udp --dport "$fallback_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p tcp --dport 80 -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p tcp --dport 80 -j ACCEPT > /dev/null 2>&1 + fi + + if ! iptables -C INPUT -p udp --dport 80 -j ACCEPT >/dev/null 2>&1; then + iptables -A INPUT -p udp --dport 80 -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p tcp --dport "$listen_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p tcp --dport "$listen_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p udp --dport "$listen_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p udp --dport "$listen_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p tcp --dport "$override_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p tcp --dport "$override_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p udp --dport "$override_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p udp --dport "$override_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p tcp --dport "$fallback_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p tcp --dport "$fallback_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p udp --dport "$fallback_port" -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p udp --dport "$fallback_port" -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p tcp --dport 80 -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT > /dev/null 2>&1 + fi + + if ! ip6tables -C INPUT -p udp --dport 80 -j ACCEPT >/dev/null 2>&1; then + ip6tables -A INPUT -p udp --dport 80 -j ACCEPT > /dev/null 2>&1 + fi + + if [[ -e /etc/iptables/rules.v4 ]]; then + iptables-save > /etc/iptables/rules.v4 + elif [[ -e /etc/sysconfig/iptables ]]; then + iptables-save > /etc/sysconfig/iptables + fi + + if [[ -e /etc/iptables/rules.v6 ]]; then + ip6tables-save > /etc/iptables/rules.v6 + elif [[ -e /etc/sysconfig/ip6tables ]]; then + ip6tables-save > /etc/sysconfig/ip6tables + fi + echo "Firewall configuration has been updated." + ;; + firewalld) + if ! firewall-cmd --zone=public --list-ports | grep -q "$listen_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$listen_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$listen_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$listen_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$override_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$override_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$override_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$override_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$fallback_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$fallback_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$fallback_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$fallback_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "80/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port=80/tcp --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "80/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port=80/udp --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$listen_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$listen_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$listen_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$listen_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$override_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$override_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$override_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$override_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$fallback_port/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$fallback_port/tcp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "$fallback_port/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port="$fallback_port/udp" --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "80/tcp" 2>/dev/null; then + firewall-cmd --zone=public --add-port=80/tcp --permanent > /dev/null 2>&1 + fi + + if ! firewall-cmd --zone=public --list-ports | grep -q "80/udp" 2>/dev/null; then + firewall-cmd --zone=public --add-port=80/udp --permanent > /dev/null 2>&1 + fi + firewall-cmd --reload + echo "Firewall configuration has been updated." + ;; + esac +} + +function create_sing_box_folders() { + local folders=("/usr/local/etc/sing-box" "/etc/ssl/private") + for folder in "${folders[@]}"; do + if [[ ! -d "$folder" ]]; then + mkdir -p "$folder" + [ "$folder" = "/usr/local/etc/sing-box" ] && touch "$folder/config.json" + fi + done +} + +function create_juicity_folder() { + local folders=("/usr/local/etc/juicity" "/etc/ssl/private") + for folder in "${folders[@]}"; do + if [[ ! -d "$folder" ]]; then + mkdir -p "$folder" + [ "$folder" = "/usr/local/etc/juicity" ] && touch "$folder/config.json" + fi + done +} + +function ensure_clash_yaml() { + local clash_yaml="/usr/local/etc/sing-box/clash.yaml" + if [ ! -e "$clash_yaml" ]; then + touch "$clash_yaml" + fi +} + +function check_config_file_existence() { + local config_file="/usr/local/etc/sing-box/config.json" + if [ ! -f "$config_file" ]; then + echo -e "${RED}sing-box 配置文件不存在,请先搭建节点!${NC}" + exit 1 + fi +} + +function generate_naive_random_filename() { + local dir="/usr/local/etc/sing-box" + local filename="" + while true; do + random_value=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 5 | head -n 1) + filename="naive_client_${random_value}.json" + if [ ! -e "${dir}/${filename}" ]; then + touch "${dir}/${filename}" + naive_client_filename="${dir}/${filename}" + break + fi + done +} + +function get_temp_config_file() { + temp_file=$(mktemp) + curl -sSL "https://api.zeroteam.top/warp?format=sing-box" > "$temp_file" +} + +function install_sing_box() { + if [[ -f "/usr/local/bin/sing-box" && -f "/usr/local/etc/sing-box/config.json" ]]; then + return 1 + else + get_local_ip + configure_dns64 + select_sing_box_install_option + configure_sing_box_service + create_sing_box_folders + fi +} + +function configure_dns64() { + if [[ -n $ip_v4 ]]; then + return + fi + if [[ -n $ip_v6 ]]; then + echo "Check that the machine is IPv6 single-stack network, configure DNS64..." + sed -i '/^nameserver /s/^/#/' /etc/resolv.conf + echo "nameserver 2001:67c:2b0::4" >> /etc/resolv.conf + echo "nameserver 2001:67c:2b0::6" >> /etc/resolv.conf + echo "DNS64 configuration is complete." + fi +} + +function enable_bbr() { + if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf; then + echo "BBR is already enabled, skipping configuration." + return + fi + while true; do + read -p "是否开启 BBR (Y/N,默认N)? " -i "N" response + response=${response:-"N"} + if [[ $response == "y" || $response == "Y" ]]; then + echo "Enable BBR..." + echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf + echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf + sysctl -p > /dev/null + echo "BBR has been enabled" + break + elif [[ $response == "n" || $response == "N" ]]; then + echo "BBR will not be enabled." + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function select_sing_box_install_option() { + while true; do + echo "请选择 sing-box 的安装方式(默认1):" + echo "1). 下载安装 sing-box(Latest 版本)" + echo "2). 下载安装 sing-box(Beta 版本)" + echo "3). 编译安装 sing-box(完整功能版本)" + read -p "请选择 [1-2]: " install_option + install_option="${install_option:-1}" + case $install_option in + 1) + install_latest_sing_box + break + ;; + 2) + install_Pre_release_sing_box + break + ;; + 3) + install_go + compile_install_sing_box + break + ;; + *) + echo -e "${RED}无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function install_go() { + if ! command -v go &> /dev/null; then + echo "Downloading Go..." + local go_arch + case $(uname -m) in + x86_64) + go_arch="amd64" + ;; + i686) + go_arch="386" + ;; + aarch64) + go_arch="arm64" + ;; + armv6l) + go_arch="armv6l" + ;; + *) + echo -e "${RED}不支持的架构: $(uname -m)${NC}" + exit 1 + ;; + esac + local go_version + go_version=$(curl -sL "https://golang.org/VERSION?m=text" | grep -o 'go[0-9]\+\.[0-9]\+\.[0-9]\+') + local go_download_url="https://go.dev/dl/$go_version.linux-$go_arch.tar.gz" + wget -qO- "$go_download_url" | tar -xz -C /usr/local + echo 'export PATH=$PATH:/usr/local/go/bin' | tee -a /etc/profile >/dev/null + source /etc/profile + go version + echo "Go has been installed." + else + echo "Go is already installed, skipping installation." + fi +} + +function compile_install_sing_box() { + local go_install_command="go install -v -tags \ +with_quic,\ +with_grpc,\ +with_dhcp,\ +with_wireguard,\ +with_shadowsocksr,\ +with_ech,\ +with_utls,\ +with_reality_server,\ +with_acme,\ +with_clash_api,\ +with_v2ray_api,\ +with_gvisor,\ +with_lwip \ +github.com/sagernet/sing-box/cmd/sing-box@latest" + echo "Compiling and installing sing-box, please wait..." + $go_install_command + if [[ $? -eq 0 ]]; then + mv ~/go/bin/sing-box /usr/local/bin/ + chmod +x /usr/local/bin/sing-box + echo "sing-box has been compiled and installed successfully." + else + echo -e "${RED}sing-box compilation and installation failed.${NC}" + exit 1 + fi +} + +function install_latest_sing_box() { + local arch=$(uname -m) + local url="https://api.github.com/repos/SagerNet/sing-box/releases/latest" + local download_url + case $arch in + x86_64|amd64) + download_url=$(curl -s $url | grep -o "https://github.com[^\"']*linux-amd64.tar.gz") + ;; + armv7l) + download_url=$(curl -s $url | grep -o "https://github.com[^\"']*linux-armv7.tar.gz") + ;; + aarch64|arm64) + download_url=$(curl -s $url | grep -o "https://github.com[^\"']*linux-arm64.tar.gz") + ;; + amd64v3) + download_url=$(curl -s $url | grep -o "https://github.com[^\"']*linux-amd64v3.tar.gz") + ;; + s390x) + download_url=$(curl -s $url | grep -o "https://github.com[^\"']*linux-s390x.tar.gz") + ;; + *) + echo -e "${RED}不支持的架构:$arch${NC}" + return 1 + ;; + esac + if [ -n "$download_url" ]; then + echo "Downloading Sing-Box..." + wget -qO sing-box.tar.gz "$download_url" 2>&1 >/dev/null + tar -xzf sing-box.tar.gz -C /usr/local/bin --strip-components=1 + rm sing-box.tar.gz + chmod +x /usr/local/bin/sing-box + echo "Sing-Box installed successfully." + else + echo -e "${RED}Unable to retrieve the download URL for Sing-Box.${NC}" + return 1 + fi +} + +function install_Pre_release_sing_box() { + local arch=$(uname -m) + local url="https://api.github.com/repos/SagerNet/sing-box/releases" + local download_url + case $arch in + x86_64|amd64) + download_url=$(curl -s "$url" | jq -r '.[] | select(.prerelease == true) | .assets[] | select(.browser_download_url | contains("linux-amd64.tar.gz")) | .browser_download_url' | head -n 1) + ;; + armv7l) + download_url=$(curl -s "$url" | jq -r '.[] | select(.prerelease == true) | .assets[] | select(.browser_download_url | contains("linux-armv7.tar.gz")) | .browser_download_url' | head -n 1) + ;; + aarch64|arm64) + download_url=$(curl -s "$url" | jq -r '.[] | select(.prerelease == true) | .assets[] | select(.browser_download_url | contains("linux-arm64.tar.gz")) | .browser_download_url' | head -n 1) + ;; + amd64v3) + download_url=$(curl -s "$url" | jq -r '.[] | select(.prerelease == true) | .assets[] | select(.browser_download_url | contains("linux-amd64v3.tar.gz")) | .browser_download_url' | head -n 1) + ;; + s390x) + download_url=$(curl -s "$url" | jq -r '.[] | select(.prerelease == true) | .assets[] | select(.browser_download_url | contains("linux-s390x.tar.gz")) | .browser_download_url' | head -n 1) + ;; + *) + echo -e "${RED}不支持的架构:$arch${NC}" + return 1 + ;; + esac + if [ -n "$download_url" ]; then + echo "Downloading Sing-Box..." + wget -qO sing-box.tar.gz "$download_url" 2>&1 >/dev/null + tar -xzf sing-box.tar.gz -C /usr/local/bin --strip-components=1 + rm sing-box.tar.gz + chmod +x /usr/local/bin/sing-box + + echo "Sing-Box installed successfully." + else + echo -e "${RED}Unable to get pre-release download link for Sing-Box.${NC}" + return 1 + fi +} + +function install_latest_juicity() { + local arch=$(uname -m) + case $arch in + "arm64") + arch_suffix="arm64" + ;; + "armv5") + arch_suffix="armv5" + ;; + "armv6") + arch_suffix="armv6" + ;; + "armv7") + arch_suffix="armv7" + ;; + "mips") + arch_suffix="mips32" + ;; + "mipsel") + arch_suffix="mips32le" + ;; + "mips64") + arch_suffix="mips64" + ;; + "mips64el") + arch_suffix="mips64le" + ;; + "riscv64") + arch_suffix="riscv64" + ;; + "i686") + arch_suffix="x86_32" + ;; + "x86_64") + if [ -n "$(grep avx2 /proc/cpuinfo)" ]; then + arch_suffix="x86_64_v3_avx2" + else + arch_suffix="x86_64_v2_sse" + fi + ;; + *) + echo "Unsupported architecture: $arch" + return 1 + ;; + esac + local github_api_url="https://api.github.com/repos/juicity/juicity/releases/latest" + local download_url=$(curl -s "$github_api_url" | grep "browser_download_url.*$arch_suffix.zip\"" | cut -d '"' -f 4) + local temp_dir=$(mktemp -d) + local install_path="/usr/local/bin/juicity-server" + echo "Downloading the latest version of juicity-server..." + wget -P "$temp_dir" "$download_url" >/dev/null 2>&1 + unzip "$temp_dir/*.zip" -d "$temp_dir" >/dev/null 2>&1 + mv "$temp_dir/juicity-server" "$install_path" >/dev/null 2>&1 + chmod +x /usr/local/bin/juicity-server + echo "juicity-server has been downloaded." + rm -rf "$temp_dir" +} + +function configure_sing_box_service() { + echo "Configuring sing-box startup service..." + local service_file="/etc/systemd/system/sing-box.service" + if [[ -f $service_file ]]; then + rm "$service_file" + fi + local service_config='[Unit] +Description=sing-box service +Documentation=https://sing-box.sagernet.org +After=network.target nss-lookup.target + +[Service] +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH +ExecStart=/usr/local/bin/sing-box run -c /usr/local/etc/sing-box/config.json +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=10s +LimitNOFILE=infinity + +[Install] +WantedBy=multi-user.target' + echo "$service_config" >"$service_file" + echo "sing-box startup service has been configured." +} + +function configure_juicity_service() { + echo "Configuring juicity startup service..." + local service_file="/etc/systemd/system/juicity.service" + if [[ -f $service_file ]]; then + rm "$service_file" + fi + local service_config='[Unit] +Description=juicity-server Service +Documentation=https://github.com/juicity/juicity +After=network.target nss-lookup.target + +[Service] +Type=simple +User=root +Environment=QUIC_GO_ENABLE_GSO=true +ExecStart=/usr/local/bin/juicity-server run -c /usr/local/etc/juicity/config.json --disable-timestamp +Restart=on-failure +LimitNPROC=512 +LimitNOFILE=infinity + +[Install] +WantedBy=multi-user.target' + echo "$service_config" >"$service_file" + echo "juicity startup service has been configured." +} + +function set_listen_port() { + while true; do + read -p "请输入监听端口 (默认443): " new_listen_port + new_listen_port=${new_listen_port:-443} + if [[ $new_listen_port =~ ^[1-9][0-9]{0,4}$ && $new_listen_port -le 65535 ]]; then + check_result=$(netstat -tulpn | grep -E "\b${new_listen_port}\b") + if [ -z "$check_result" ]; then + echo "监听端口:$new_listen_port" + break + else + echo -e "${RED}错误:端口已被占用,请选择其他端口!${NC}" >&2 + fi + else + echo -e "${RED}错误:端口范围1-65535,请重新输入!${NC}" >&2 + fi + done + listen_port="$new_listen_port" +} + +function set_user_name() { + while true; do + read -p "请输入用户名 (默认随机生成): " new_user_name + if [[ -z "$new_user_name" ]]; then + new_user_name=$(sing-box generate rand --base64 6 2>/dev/null || openssl rand -base64 5) + echo "用户名:$new_user_name" + break + elif [[ ! -z "$new_user_name" ]]; then + break + fi + done + user_names+=("$new_user_name") +} + +function set_user_password() { + while true; do + read -p "请输入密码(默认随机生成): " new_user_password + if [[ -z "$new_user_password" ]]; then + new_user_password=$(sing-box generate rand --base64 9 2>/dev/null || openssl rand -base64 9) + echo "密码:$new_user_password" + break + elif [[ ! -z "$new_user_password" ]]; then + break + fi + done + user_passwords+=("$new_user_password") +} + +function set_ss_password() { + while true; do + read -p "请输入 Shadowsocks 密码(默认随机生成): " ss_user_password + if [[ -z $ss_user_password ]]; then + if [[ $encryption_choice == 1 || $encryption_choice == 2 ]]; then + ss_password=$(sing-box generate rand --base64 32) + echo "Shadowsocks 密码: $ss_password" + else + ss_password=$(sing-box generate rand --base64 16) + echo "Shadowsocks 密码: $ss_password" + fi + ss_passwords+=("$ss_password") + break + elif [[ $encryption_choice == 1 || $encryption_choice == 2 ]] && [[ ${#ss_user_password} -eq 32 ]]; then + ss_password="$ss_user_password" + echo "Shadowsocks 密码: $ss_password" + ss_passwords+=("$ss_password") + break + elif [[ $encryption_choice != 1 && $encryption_choice != 2 ]] && [[ ${#ss_user_password} -eq 16 ]]; then + ss_password="$ss_user_password" + echo "Shadowsocks 密码: $ss_password" + ss_passwords+=("$ss_password") + break + else + echo -e "${RED}错误:密码长度不符合要求,请重新输入!${NC}" + fi + done +} + +function set_stls_password() { + while true; do + read -p "请输入 ShadowTLS 密码(默认随机生成): " stls_user_password + if [[ -z $stls_user_password ]]; then + if [[ $encryption_choice == 1 || $encryption_choice == 2 ]]; then + stls_password=$(sing-box generate rand --base64 32) + echo "ShadowTLS 密码: $stls_password" + else + stls_password=$(sing-box generate rand --base64 16) + echo "ShadowTLS 密码: $stls_password" + fi + stls_passwords+=("$stls_password") + break + elif [[ $encryption_choice == 1 || $encryption_choice == 2 ]] && [[ ${#stls_user_password} -eq 32 ]]; then + stls_password="$stls_user_password" + echo "ShadowTLS 密码: $stls_password" + stls_passwords+=("$stls_password") + break + elif [[ $encryption_choice != 1 && $encryption_choice != 2 ]] && [[ ${#stls_user_password} -eq 16 ]]; then + stls_password="$stls_user_password" + echo "ShadowTLS 密码: $stls_password" + stls_passwords+=("$stls_password") + break + else + echo -e "${RED}错误:密码长度不符合要求,请重新输入!${NC}" + fi + done +} + +function set_up_speed() { + while true; do + read -p "请输入上行速率 (默认50): " new_up_mbps + new_up_mbps=${new_up_mbps:-50} + if [[ $new_up_mbps =~ ^[0-9]+$ ]]; then + echo "上行速率:$new_up_mbps Mbps" + break + else + echo -e "${RED}错误:请输入数字作为上行速率!${NC}" + fi + done + up_mbps="$new_up_mbps" +} + +function set_down_speed() { + while true; do + read -p "请输入下行速率 (默认100): " new_down_mbps + new_down_mbps=${new_down_mbps:-100} + if [[ $new_down_mbps =~ ^[0-9]+$ ]]; then + echo "下行速率:$new_down_mbps Mbps" + break + else + echo -e "${RED}错误:请输入数字作为下行速率!${NC}" + fi + done + down_mbps="$new_down_mbps" +} + +function set_uuid() { + while true; do + read -p "请输入UUID(默认随机生成): " new_user_uuid + if [ -z "$new_user_uuid" ]; then + new_user_uuid=$(sing-box generate uuid 2>/dev/null || openssl rand -hex 16 | awk '{print substr($1,1,8) "-" substr($1,9,4) "-" substr($1,13,4) "-" substr($1,17,4) "-" substr($1,21)}') + fi + if [[ $new_user_uuid =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then + echo "UUID:$new_user_uuid" + break + else + echo -e "${RED}无效的UUID格式,请重新输入!${NC}" + fi + done + user_uuids+=("$new_user_uuid") +} + +function set_override_port() { + while true; do + read -p "请输入目标端口 (默认443): " new_override_port + new_override_port=${new_override_port:-443} + if [[ $new_override_port =~ ^[1-9][0-9]{0,4}$ && $new_override_port -le 65535 ]]; then + echo "目标端口: $new_override_port" + break + else + echo -e "${RED}错误:端口范围1-65535,请重新输入!${NC}" + fi + done + override_port="$new_override_port" +} + +function generate_unique_tag() { + local config_file="/usr/local/etc/sing-box/config.json" + while true; do + random_tag=$(head /dev/urandom | tr -dc 'a-z0-9' | fold -w 8 | head -n 1) + tag_label="${random_tag}-in" + if ! grep -qE "\"tag\":\\s*\"$tag_label\"(,|$)" "$config_file"; then + break + fi + done +} + +function set_override_address() { + while true; do + read -p "请输入目标地址(IP或域名): " target_address + if [[ -z "$target_address" ]]; then + echo -e "${RED}错误:目标地址不能为空!${NC}" + continue + fi + if ( [[ $target_address =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && [[ $(grep -o '\.' <<< "$target_address" | wc -l) -eq 3 ]] ) || ( [[ $target_address =~ ^[a-fA-F0-9:]+$ ]] && [[ $(grep -o ':' <<< "$target_address" | wc -l) -ge 2 ]] ); then + break + else + resolved_ips=$(host -t A "$target_address" | awk '/has address/ { print $4 }') + + if [[ -n "$resolved_ips" ]] && ( [[ "$resolved_ips" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$resolved_ips" =~ ^[a-fA-F0-9:]+$ ]] ); then + break + else + echo -e "${RED}错误:请输入有效的 IP 地址或域名!${NC}" + fi + fi + done +} + +function set_server_name() { + while true; do + read -p "请输入可用的 ServerName 列表 (默认为 nijigen-works.jp): " user_input + if [[ -z "$user_input" ]]; then + server_name="nijigen-works.jp" + echo "ServerName:$server_name" + break + else + server_name="$user_input" + echo "ServerName:$server_name" + echo "Verifying server's TLS version support..." + if command -v openssl >/dev/null 2>&1; then + local openssl_output=$(timeout 10s openssl s_client -connect "$server_name:443" -tls1_3 2>&1) + if [[ $openssl_output == *"TLS_AES_256_GCM_SHA384"* || \ + $openssl_output == *"TLS_AES_128_GCM_SHA256"* || \ + $openssl_output == *"TLS_CHACHA20_POLY1305_SHA256"* || \ + $openssl_output == *"TLS_AES_128_CCM_SHA256"* || \ + $openssl_output == *"TLS_AES_128_CCM_8_SHA256"* ]]; then + break + else + echo -e "${RED}该网址不支持 TLS 1.3,请重新输入!${NC}" + fi + else + echo "OpenSSL is not installed, cannot verify TLS support." + break + fi + fi + done +} + +function set_target_server() { + while true; do + read -p "请输入目标网站地址(默认为 nijigen-works.jp): " user_input + if [[ -z "$user_input" ]]; then + target_server="nijigen-works.jp" + echo "目标网址:$target_server" + break + else + target_server="$user_input" + echo "目标网址:$target_server" + echo "Verifying server's TLS version support..." + if command -v openssl >/dev/null 2>&1; then + local openssl_output=$(timeout 10s openssl s_client -connect "$target_server:443" -tls1_3 2>&1) + if [[ $openssl_output == *"TLS_AES_256_GCM_SHA384"* || \ + $openssl_output == *"TLS_AES_128_GCM_SHA256"* || \ + $openssl_output == *"TLS_CHACHA20_POLY1305_SHA256"* || \ + $openssl_output == *"TLS_AES_128_CCM_SHA256"* || \ + $openssl_output == *"TLS_AES_128_CCM_8_SHA256"* ]]; then + break + else + echo -e "${RED}该目标网站地址不支持 TLS 1.3,请重新输入!${NC}" + fi + else + echo "OpenSSL is not installed, cannot verify TLS support." + break + fi + fi + done +} + +function get_local_ip() { + local local_ip_v4 + local local_ip_v6 + local_ip_v4=$(curl -s4 https://api.myip.com | grep -o '"ip":"[^"]*' | awk -F ':"' '{print $2}') + if [[ -n "$local_ip_v4" ]]; then + ip_v4="$local_ip_v4" + else + local_ip_v4=$(curl -s4 icanhazip.com) + if [[ -n "$local_ip_v4" ]]; then + ip_v4="$local_ip_v4" + fi + fi + local_ip_v6=$(curl -s6 https://api.myip.com | grep -o '"ip":"[^"]*' | awk -F ':"' '{print $2}') + if [[ -n "$local_ip_v6" ]]; then + ip_v6="$local_ip_v6" + else + local_ip_v6=$(curl -s6 icanhazip.com) + if [[ -n "$local_ip_v6" ]]; then + ip_v6="$local_ip_v6" + fi + fi + if [[ -z "$ip_v4" && -z "$ip_v6" ]]; then + echo -e "${RED}无法获取本机IP地址!${NC}" + fi +} + +function get_ech_keys() { + local input_file="/etc/ssl/private/ech.tmp" + local output_file="/etc/ssl/private/ech.pem" + sing-box generate ech-keypair [--pq-signature-schemes-enabled] > "$input_file" + IFS=$'\n' read -d '' -ra lines < "$input_file" + exec 3>"$output_file" + in_ech_keys_section=false + in_ech_configs_section=false + for line in "${lines[@]}"; do + if [[ "$line" == *"BEGIN ECH KEYS"* ]]; then + in_ech_keys_section=true + ech_key+=" \"$line\",\n" + elif [[ "$line" == *"END ECH KEYS"* ]]; then + in_ech_keys_section=false + ech_key+=" \"$line\"" + elif [[ "$line" == *"BEGIN ECH CONFIGS"* ]]; then + in_ech_configs_section=true + ech_config+=" \"$line\",\n" + elif [[ "$line" == *"END ECH CONFIGS"* ]]; then + in_ech_configs_section=false + ech_config+=" \"$line\"" + elif [ "$in_ech_keys_section" = true ]; then + ech_key+=" \"$line\",\n" + elif [ "$in_ech_configs_section" = true ]; then + ech_config+=" \"$line\",\n" + else + echo "\"$line\"," >&3 + fi + done + exec 3>&- + rm "$input_file" +} + +function get_domain() { + while true; do + read -p "请输入域名(关闭Cloudflare代理): " user_domain + resolved_ipv4=$(dig +short A "$user_domain" 2>/dev/null) + resolved_ipv6=$(dig +short AAAA "$user_domain" 2>/dev/null) + if [[ -z $user_domain ]]; then + echo -e "${RED}错误:域名不能为空,请重新输入!${NC}" + else + if [[ ("$resolved_ipv4" == "$ip_v4" && ! -z "$resolved_ipv4") || ("$resolved_ipv6" == "$ip_v6" && ! -z "$resolved_ipv6") ]]; then + break + else + if [[ -z "$resolved_ipv4" && -n "$ip_v4" ]]; then + resolved_ip_v4=$(ping -4 "$user_domain" -c 1 2>/dev/null | sed '1{s/[^(]*(//;s/).*//;q}') + if [[ ("$resolved_ip_v4" == "$ip_v4" && ! -z "$resolved_ip_v4") ]]; then + break + fi + fi + if [[ -z "$resolved_ipv6" && -n "$ip_v6" ]]; then + resolved_ip_v6=$(ping -6 "$user_domain" -c 1 2>/dev/null | sed '1{s/[^(]*(//;s/).*//;q}') + if [[ ("$resolved_ip_v6" == "$ip_v6" && ! -z "$resolved_ip_v6") ]]; then + break + fi + fi + echo -e "${RED}错误:域名未绑定本机IP,请重新输入!${NC}" + fi + fi + done + domain="$user_domain" +} + +function verify_domain() { + new_domain=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id" \ + -H "Authorization: Bearer $api_token" | jq -r '.result.name') + if [[ $new_domain =~ \.(tk|ml|ga|gq|cf)$ ]]; then + echo -e "${RED}您的域名为$new_domain,该域名不支持使用 CloudFlare 的 API 申请证书,请选择其他方式申请证书!${NC}" + domain_supported=false + else + while true; do + read -p "请输入主域名前缀(若为空则使用主域名申请证书,不需要在 CloudFlare 添加 DNS 解析记录): " domain_prefix + + if [ -z "$domain_prefix" ]; then + domain="$new_domain" + record_name="$domain_prefix" + break + else + domain="$domain_prefix"."$new_domain" + record_name="$domain_prefix" + break + fi + done + domain_supported=true + fi +} + +function set_dns_record() { + if [[ -z "$record_name" ]]; then + name_value="@" + else + name_value="$record_name" + fi + if [[ -n "$ip_v4" ]]; then + record_content=" $ip_v4" + record_type="A" + elif [[ -z "$ip_v4" && -n "$ip_v6" ]]; then + record_content=" $ip_v6" + record_type="AAAA" + fi + curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_Zone_ID/dns_records" \ + -H "Authorization: Bearer $CF_Token" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"$record_type\",\"name\":\"$name_value\",\"content\":\"$record_content\",\"ttl\":120,\"proxied\":false}" >/dev/null +} + +function get_api_token() { + while true; do + read -p "请输入 CloudFlare 的限制性 API 令牌: " api_token + if [[ ! $api_token =~ ^[A-Za-z0-9_-]{40}$ ]]; then + echo -e "${RED}API令牌格式不正确,请重新输入!${NC}" + else + export CF_Token="$api_token" + break + fi + done +} + +function get_zone_id() { + while true; do + read -p "请输入 CloudFlare 的区域 ID: " zone_id + if [[ ! $zone_id =~ ^[a-z0-9]{32}$ ]]; then + echo -e "${RED}CloudFlare 的区域 ID 格式不正确,请重新输入!${NC}" + else + export CF_Zone_ID="$zone_id" + break + fi + done +} + +function get_api_email() { + while true; do + read -p "请输入 CloudFlare 的登录邮箱: " api_email + if [[ ! $api_email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]; then + echo -e "${RED}邮箱格式不正确,请重新输入!${NC}" + else + export CF_Email="$api_email" + break + fi + done +} + +function set_fake_domain() { + while true; do + read -p "请输入伪装网址(默认: www.fan-2000.com): " fake_domain + fake_domain=${fake_domain:-"www.fan-2000.com"} + if curl --output /dev/null --silent --head --fail "$fake_domain"; then + echo "伪装网址: $fake_domain" + break + else + echo -e "${RED}伪装网址无效或不可用,请重新输入!${NC}" + fi + done +} + +function set_certificate_path() { + while true; do + read -p "请输入 PEM 证书位置: " certificate_path_input + if [[ ! -f "$certificate_path_input" ]]; then + echo -e "${RED}错误:证书文件不存在,请重新输入!${NC}" + continue + fi + certificate_file=$(basename "$certificate_path_input") + allowed_extensions=("crt" "pem") + if [[ ! "${allowed_extensions[@]}" =~ "${certificate_file##*.}" ]]; then + echo -e "${RED}错误:不支持的证书格式,请配置.crt或.pem格式的证书文件!${NC}" + continue + fi + certificate_path="$certificate_path_input" + break + done +} + +function set_private_key_path() { + while true; do + read -p "请输入 PEM 私钥位置: " private_key_path_input + if [[ ! -f "$private_key_path_input" ]]; then + echo -e "${RED}错误:私钥文件不存在,请重新输入!${NC}" + continue + fi + private_key_file=$(basename "$private_key_path_input") + allowed_extensions=("key" "pem") + if [[ ! "${allowed_extensions[@]}" =~ "${private_key_file##*.}" ]]; then + echo -e "${RED}错误:不支持的私钥格式,请配置.key或.pem格式的私钥文件!${NC}" + continue + fi + private_key_path="$private_key_path_input" + break + done +} + +function apply_certificate() { + certificate_path="/etc/ssl/private/"$domain".crt" + private_key_path="/etc/ssl/private/"$domain".key" + local has_ipv4=false + local ca_servers=("letsencrypt" "zerossl") + local return_to_menu=false + if [[ -n "$ip_v4" ]]; then + has_ipv4=true + fi + echo "Requesting a certificate..." + curl -s https://get.acme.sh | sh -s email=example@gmail.com 2>&1 | tail -n 1 + alias acme.sh=~/.acme.sh/acme.sh + for ca_server in "${ca_servers[@]}"; do + echo "Requesting a certificate from $ca_server..." + ~/.acme.sh/acme.sh --set-default-ca --server "$ca_server" + if $has_ipv4; then + result=$(~/.acme.sh/acme.sh --issue -d "$domain" --standalone -k ec-256 2>&1) + else + result=$(~/.acme.sh/acme.sh --issue -d "$domain" --standalone -k ec-256 --listen-v6 2>&1) + fi + if [[ $result == *"force"* ]]; then + if $has_ipv4; then + result=$(~/.acme.sh/acme.sh --issue -d "$domain" --standalone -k ec-256 --force 2>&1) + else + result=$(~/.acme.sh/acme.sh --issue -d "$domain" --standalone -k ec-256 --listen-v6 --force 2>&1) + fi + fi + if [[ $result == *"log"* || $result == *"debug"* || $result == *"error"* ]]; then + echo -e "${RED}$result ${NC}" + continue + fi + if [[ $? -eq 0 ]]; then + echo "Installing the certificate..." + ~/.acme.sh/acme.sh --install-cert -d "$domain" --ecc --key-file "$private_key_path" --fullchain-file "$certificate_path" + break + else + echo -e "${RED}Failed to obtain a certificate from $ca_server!${NC}" + return_to_menu=true + fi + done + if [ "$return_to_menu" = true ]; then + echo -e "${RED}证书申请失败,请使用其它方法申请证书!${NC}" + return 1 + fi +} + +function Apply_api_certificate() { + certificate_path="/etc/ssl/private/"$domain".crt" + private_key_path="/etc/ssl/private/"$domain".key" + local has_ipv4=false + local ca_servers=("letsencrypt" "zerossl") + if [[ -n "$ip_v4" ]]; then + has_ipv4=true + fi + echo "Requesting a certificate..." + curl -s https://get.acme.sh | sh -s email=example@gmail.com 2>&1 | tail -n 1 + alias acme.sh=~/.acme.sh/acme.sh + for ca_server in "${ca_servers[@]}"; do + echo "Requesting a certificate from $ca_server..." + ~/.acme.sh/acme.sh --set-default-ca --server "$ca_server" + if $has_ipv4; then + result=$(~/.acme.sh/acme.sh --issue --dns dns_cf -d "$domain" -k ec-256 2>&1) + else + result=$(~/.acme.sh/acme.sh --issue --dns dns_cf -d "$domain" -k ec-256 --listen-v6 2>&1) + fi + if [[ $result == *"log"* || $result == *"debug"* || $result == *"error"* || $result == *"force"* ]]; then + echo -e "${RED}$result ${NC}" + return_to_menu=true + continue + fi + if [[ $? -eq 0 ]]; then + echo "Installing the certificate..." + ~/.acme.sh/acme.sh --install-cert -d "$domain" --ecc --key-file "$private_key_path" --fullchain-file "$certificate_path" + break + else + echo -e "${RED}Failed to obtain a certificate from $ca_server!${NC}" + return_to_menu=true + fi + done + if [ "$return_to_menu" = true ]; then + echo -e "${RED}证书申请失败,请使用其它方法申请证书!${NC}" + return 1 + fi +} + +function Reapply_certificates() { + local tls_info_file="/usr/local/etc/sing-box/tls_info.json" + local has_ipv4=false + if [ -n "$ip_v4" ]; then + has_ipv4=true + fi + if ! command -v acme.sh &>/dev/null; then + curl -s https://get.acme.sh | sh -s email=example@gmail.com + fi + alias acme.sh=~/.acme.sh/acme.sh + echo "Setting CA server to Let's Encrypt..." + ~/.acme.sh/acme.sh --set-default-ca --server "letsencrypt" + jq -c '.[]' "$tls_info_file" | while read -r tls_info; do + server_name=$(echo "$tls_info" | jq -r '.server_name') + key_path=$(echo "$tls_info" | jq -r '.key_path') + certificate_path=$(echo "$tls_info" | jq -r '.certificate_path') + echo "Requesting certificate for $server_name..." + result=$( + if $has_ipv4; then + ~/.acme.sh/acme.sh --issue --dns dns_cf -d "$server_name" -k ec-256 --force + else + ~/.acme.sh/acme.sh --issue --dns dns_cf -d "$server_name" -k ec-256 --listen-v6 --force + fi + ) + if [[ "$result" =~ "Cert success." ]]; then + echo "Certificate for $server_name has been applied using Cloudflare DNS verification." + else + echo "Cloudflare DNS verification failed for $server_name. Trying standalone verification..." + result=$( + if $has_ipv4; then + ~/.acme.sh/acme.sh --issue -d "$server_name" --standalone --force + else + ~/.acme.sh/acme.sh --issue -d "$server_name" --standalone --listen-v6 --force + fi + ) + if [[ "$result" =~ "BEGIN CERTIFICATE" && "$result" =~ "END CERTIFICATE" ]]; then + echo "Certificate for $server_name has been applied using Let's Encrypt CA." + else + echo "Failed to obtain certificate for $server_name using standalone verification as well." + return 1 + fi + fi + ~/.acme.sh/acme.sh --install-cert -d "$server_name" --ecc --key-file "$key_path" --fullchain-file "$certificate_path" + echo "Certificate for $server_name has been installed." + done + rm -f "$tls_info_file" +} + +function generate_private_key() { + while true; do + read -p "请输入私钥 (默认随机生成私钥): " local_private_key + if [[ -z "$local_private_key" ]]; then + local keypair_output=$(sing-box generate reality-keypair) + local_private_key=$(echo "$keypair_output" | awk -F: '/PrivateKey/{gsub(/ /, "", $2); print $2}') + local_public_key=$(echo "$keypair_output" | awk -F: '/PublicKey/{gsub(/ /, "", $2); print $2}') + echo "private_key:$local_private_key" + echo "public_key:$local_public_key" + break + else + if [[ "$local_private_key" =~ ^[A-Za-z0-9_\-]{43}$ ]]; then + read -p "请输入公钥: " local_public_key + if ! [[ "$local_public_key" =~ ^[A-Za-z0-9_\-]{43}$ ]]; then + echo -e "${RED}无效的公钥,请重新输入!${NC}" + else + break + fi + else + echo -e "${RED}无效的私钥,请重新输入!${NC}" + fi + fi + done + public_key="$local_public_key" + private_key="$local_private_key" +} + +function create_self_signed_cert() { + while true; do + read -p "请输入要用于自签名证书的域名(默认为 bing.com): " user_domain + domain_name=${user_domain:-"bing.com"} + if curl --output /dev/null --silent --head --fail "$domain_name"; then + openssl req -x509 -nodes -newkey ec:<(openssl ecparam -name prime256v1) -keyout /etc/ssl/private/$domain_name.key -out /etc/ssl/private/$domain_name.crt -subj "/CN=$domain_name" -days 36500 + chmod 777 /etc/ssl/private/$domain_name.key + chmod 777 /etc/ssl/private/$domain_name.crt + break + else + echo -e "${RED}无效的域名或域名不可用,请输入有效的域名!${NC}" + fi + done + certificate_path="/etc/ssl/private/$domain_name.crt" + private_key_path="/etc/ssl/private/$domain_name.key" +} + +function select_encryption_method() { + while true; do + read -p "请选择加密方式(默认1): +1). 2022-blake3-chacha20-poly1305 +2). 2022-blake3-aes-256-gcm +3). 2022-blake3-aes-128-gcm +4). xchacha20-ietf-poly1305 +5). chacha20-ietf-poly1305 +6). aes-256-gcm +7). aes-192-gcm +8). aes-128-gcm +请选择[1-8]: " encryption_choice + encryption_choice=${encryption_choice:-1} + case $encryption_choice in + 1) + ss_method="2022-blake3-chacha20-poly1305" + ss_password=$(sing-box generate rand --base64 32) + shadowtls_password=$(sing-box generate rand --base64 32) + break + ;; + 2) + ss_method="2022-blake3-aes-256-gcm" + ss_password=$(sing-box generate rand --base64 32) + shadowtls_password=$(sing-box generate rand --base64 32) + break + ;; + 3) + ss_method="2022-blake3-aes-128-gcm" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + + 4) + ss_method="xchacha20-ietf-poly1305" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + 5) + ss_method="chacha20-ietf-poly1305" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + 6) + ss_method="aes-256-gcm" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + 7) + ss_method="aes-192-gcm" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + 8) + ss_method="aes-128-gcm" + ss_password=$(sing-box generate rand --base64 16) + shadowtls_password=$(sing-box generate rand --base64 16) + break + ;; + *) + echo -e "${RED}错误:无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function select_unlocked_items() { + while true; do + read -p "请选择要解锁的项目(支持多选): +1). ChatGPT +2). Netflix +3). Disney+ +4). YouTube +请选择[1-4]: " choices + if [[ "$choices" =~ ^[1234]+$ ]]; then + selected=($(echo "$choices" | sed 's/./& /g')) + break + else + echo -e "${RED}错误:无效的选择,请重新输入!${NC}" + fi + done +} + +function update_geosite_array() { + for choice in "${selected[@]}"; do + case $choice in + 1) + geosite+=("\"openai\"") + ;; + 2) + geosite+=("\"netflix\"") + ;; + 3) + geosite+=("\"disney\"") + ;; + 4) + geosite+=("\"youtube\"") + ;; + *) + echo -e "${RED}无效的选择: $choice${NC}" + ;; + esac + done +} + +function select_outbound() { + while true; do + read -p "请选择出站网络 (默认1) +1). warp-IPv4 +2). warp-IPv6 +请选择[1-2]: " outbound_choice + case $outbound_choice in + 1|"") + outbound="warp-IPv4-out" + break + ;; + 2) + outbound="warp-IPv6-out" + break + ;; + *) + echo -e "${RED}错误:无效的选项,请重新输入!${NC}" + ;; + esac + done +} + +function select_congestion_control() { + local default_congestion_control="bbr" + while true; do + read -p "请选择拥塞控制算法 (默认$default_congestion_control): +1). bbr +2). cubic +3). new_reno +请选择[1-3]: " congestion_control + + case $congestion_control in + 1) + congestion_control="bbr" + break + ;; + 2) + congestion_control="cubic" + break + ;; + 3) + congestion_control="new_reno" + break + ;; + "") + congestion_control=$default_congestion_control + break + ;; + *) + echo -e "${RED}错误:无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function select_certificate_option() { + local certificate_option + local domain_supported=false + local return_to_menu=false + while true; do + read -p "请选择证书来源 (默认1): +1). 自签证书 +2). 监听80端口申请证书(standalone模式) +3). cloudflare API 申请证书(DNS API模式) +4). 自定义证书路径 +请选择[1-4]: " certificate_option + certificate_option=${certificate_option:-1} + case $certificate_option in + 1) + if $disable_option; then + echo -e "${RED}NaiveProxy节点不支持自签证书,请使用acme申请证书!${NC}" + continue + fi + check_firewall_configuration + create_self_signed_cert + break + ;; + 2) + get_local_ip + get_domain + check_firewall_configuration + apply_certificate + if [ "$return_to_menu" == true ]; then + return_to_menu=false + continue + fi + break + ;; + 3) + get_local_ip + get_api_token + get_zone_id + get_api_email + verify_domain + set_dns_record + check_firewall_configuration + if [ "$domain_supported" == "false" ]; then + continue + else + Apply_api_certificate + if [ "$return_to_menu" == true ]; then + return_to_menu=false + continue + fi + break + fi + ;; + 4) + get_local_ip + get_domain + check_firewall_configuration + set_certificate_path + set_private_key_path + break + ;; + *) + echo -e "${RED}错误:无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function select_vmess_type() { + while true; do + read -p "请选择节点类型(默认1): +1). VMess+TCP +2). VMess+WebSocket +3). VMess+gRPC +4). VMess+HTTPUpgrade +5). VMess+TCP+TLS +6). VMess+WebSocket+TLS +7). VMess+H2C+TLS +8). VMess+gRPC+TLS +9). VMess+HTTPUpgrade+TLS +请选择 [1-9]: " node_type + case $node_type in + "" | 1) + tls_enabled=false + break + ;; + 2) + transport_ws=true + tls_enabled=false + break + ;; + 3) + transport_grpc=true + tls_enabled=false + break + ;; + 4) + transport_httpupgrade=true + tls_enabled=false + break + ;; + 5) + tls_enabled=true + break + ;; + 6) + transport_ws=true + tls_enabled=true + break + ;; + 7) + transport_http=true + tls_enabled=true + break + ;; + 8) + transport_grpc=true + tls_enabled=true + break + ;; + 9) + transport_httpupgrade=true + tls_enabled=true + break + ;; + *) + echo -e "${RED}无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function select_vless_type() { + while true; do + read -p "请选择节点类型 (默认1): +1). VLESS+TCP +2). VLESS+WebSocket +3). VLESS+gRPC +4). VLESS+HTTPUpgrade +5). VLESS+Vision+REALITY +6). VLESS+H2C+REALITY +7). VLESS+gRPC+REALITY +请选择[1-7]: " flow_option + case $flow_option in + "" | 1) + flow_type="" + break + ;; + 2) + flow_type="" + transport_ws=true + break + ;; + 3) + flow_type="" + transport_grpc=true + break + ;; + 4) + flow_type="" + transport_httpupgrade=true + break + ;; + 5) + flow_type="xtls-rprx-vision" + reality_enabled=true + break + ;; + 6) + flow_type="" + transport_http=true + reality_enabled=true + break + ;; + 7) + flow_type="" + transport_grpc=true + reality_enabled=true + break + ;; + *) + echo -e "${RED}错误的选项,请重新输入!${NC}" >&2 + ;; + esac + done +} + +function select_trojan_type() { + while true; do + read -p "请选择节点类型(默认1): +1). Trojan+TCP +2). Trojan+WebSocket +3). Trojan+gRPC +4). Trojan+HTTPUpgrade +5). Trojan+TCP+TLS +6). Trojan+WebSocket+TLS +7). Trojan+H2C+TLS +8). Trojan+gRPC+TLS +9). Trojan+HTTPUpgrade+TLS +请选择 [1-9]: " setup_type + case $setup_type in + "" | 1) + tls_enabled=false + break + ;; + 2) + transport_ws=true + tls_enabled=false + break + ;; + 3) + transport_grpc=true + tls_enabled=false + break + ;; + 4) + transport_httpupgrade=true + tls_enabled=false + break + ;; + 5) + tls_enabled=true + break + ;; + 6) + transport_ws=true + tls_enabled=true + break + ;; + 7) + transport_http=true + tls_enabled=true + break + ;; + 8) + transport_grpc=true + tls_enabled=true + break + ;; + 9) + transport_httpupgrade=true + tls_enabled=true + break + ;; + *) + echo -e "${RED}无效的选择,请重新输入!${NC}" + ;; + esac + done +} + +function set_short_id() { + while true; do + read -p "请输入 Short_Id (用于区分不同的客户端,默认随机生成): " short_id + if [[ -z "$short_id" ]]; then + short_id=$(openssl rand -hex 8) + echo "Short_Id:$short_id" + break + elif [[ "$short_id" =~ ^[0-9a-fA-F]{2,16}$ ]]; then + echo "Short_Id:$short_id" + break + else + echo "错误:请输入两到八位的十六进制字符串!" + fi + done + short_ids+=("$short_id") +} + +function set_short_ids() { + while true; do + set_short_id + for ((i=0; i<${#short_ids[@]}; i++)); do + short_id="${short_ids[$i]}" + done + read -p "是否继续添加 short id?(Y/N,默认N): " -e choice + if [[ -z "$choice" ]]; then + choice="N" + fi + if [[ "$choice" == "N" || "$choice" == "n" ]]; then + short_Ids+="\n \"$short_id\"" + break + elif [[ "$choice" == "Y" || "$choice" == "y" ]]; then + short_Ids+="\n \"$short_id\"," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function tuic_multiple_users() { + while true; do + set_user_name + set_user_password + set_uuid + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_uuid="${user_uuids[$i]}" + user_password="${user_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"uuid\": \"$user_uuid\",\n \"password\": \"$user_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"uuid\": \"$user_uuid\",\n \"password\": \"$user_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function vmess_multiple_users() { + while true; do + set_uuid + for ((i=0; i<${#user_uuids[@]}; i++)); do + user_uuid="${user_uuids[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"uuid\": \"$user_uuid\",\n \"alterId\": 0\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"uuid\": \"$user_uuid\",\n \"alterId\": 0\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function vless_multiple_users() { + while true; do + set_uuid + for ((i=0; i<${#user_uuids[@]}; i++)); do + user_uuid="${user_uuids[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"uuid\": \"$user_uuid\",\n \"flow\": \"$flow_type\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"uuid\": \"$user_uuid\",\n \"flow\": \"$flow_type\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function socks_naive_multiple_users() { + while true; do + set_user_name + set_user_password + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_password="${user_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"username\": \"$user_name\",\n \"password\": \"$user_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"username\": \"$user_name\",\n \"password\": \"$user_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function hysteria_multiple_users() { + while true; do + set_user_name + set_user_password + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_password="${user_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"auth_str\": \"$user_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"auth_str\": \"$user_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function hy2_multiple_users() { + while true; do + set_user_name + set_user_password + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_password="${user_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"password\": \"$user_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"password\": \"$user_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function trojan_multiple_users() { + while true; do + set_user_password + for ((i=0; i<${#user_passwords[@]}; i++)); do + user_password="${user_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"password\": \"$user_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"password\": \"$user_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function shadowtls_multiple_users() { + while true; do + set_user_name + set_stls_password + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + stls_password="${stls_passwords[$i]}" + done + read -p "是否继续添加用户?(Y/N,默认N): " -e add_multiple_users + if [[ -z "$add_multiple_users" ]]; then + add_multiple_users="N" + fi + if [[ "$add_multiple_users" == "N" || "$add_multiple_users" == "n" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"password\": \"$stls_password\"\n }" + break + elif [[ "$add_multiple_users" == "Y" || "$add_multiple_users" == "y" ]]; then + users+="\n {\n \"name\": \"$user_name\",\n \"password\": \"$stls_password\"\n }," + continue + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function generate_transport_config() { + if [[ "$transport_ws" = true ]]; then + read -p "请输入 ws 路径 (默认随机生成): " transport_path_input + transport_path=${transport_path_input:-/$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)} + if [[ ! "$transport_path" =~ ^/ ]]; then + transport_path="/$transport_path" + fi + transport_config="\n \"transport\": {\n \"type\": \"ws\",\n \"path\": \"$transport_path\",\n \"max_early_data\": 2048,\n \"early_data_header_name\": \"Sec-WebSocket-Protocol\"\n }," + elif [[ "$transport_httpupgrade" = true ]]; then + transport_path=${transport_path_input:-/$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)} + if [[ ! "$transport_path" =~ ^/ ]]; then + transport_path="/$transport_path" + fi + transport_config="\n \"transport\": {\n \"type\": \"httpupgrade\",\n \"path\": \"$transport_path\"\n }," + elif [[ "$transport_grpc" = true ]]; then + service_name=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8) + transport_config="\n \"transport\": {\n \"type\": \"grpc\",\n \"service_name\": \"$service_name\"\n }," + elif [[ "$transport_http" = true ]]; then + transport_config="\n \"transport\": {\n \"type\": \"http\"\n }," + else + transport_config="" + fi +} + +function generate_tls_config() { + if [[ "$tls_enabled" = true ]]; then + set_ech_config + select_certificate_option + fi + if [ -z "$domain_name" ]; then + if [ -n "$domain" ]; then + server_name="$domain" + fi + else + server_name="$domain_name" + fi + + if [[ "$tls_enabled" = true ]]; then + tls_config=",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"$server_name\",\n \"certificate_path\": \"$certificate_path\",\n \"key_path\": \"$private_key_path\"$ech_server_config\n }" + fi +} + +function set_ech_config() { + while true; do + read -p "是否开启 ECH?(Y/N,默认Y):" enable_ech + enable_ech="${enable_ech:-Y}" + if [[ "$enable_ech" == "y" || "$enable_ech" == "Y" ]]; then + get_ech_keys + enable_ech=true + ech_server_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"key\": [\n$ech_key\n ]\n }" + break + elif [[ "$enable_ech" == "n" || "$enable_ech" == "N" ]]; then + enable_ech=false + ech_server_config="" + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function generate_reality_config() { + if [[ "$reality_enabled" = true ]]; then + set_server_name + set_target_server + generate_private_key + set_short_ids + reality_config=",\n \"tls\": {\n \"enabled\": true,\n \"server_name\": \"$server_name\",\n \"reality\": {\n \"enabled\": true,\n \"handshake\": {\n \"server\": \"$target_server\",\n \"server_port\": 443\n },\n \"private_key\": \"$private_key\",\n \"short_id\": [$short_Ids\n ]\n }\n }" + fi +} + +function configure_quic_obfuscation() { + while true; do + read -p "是否开启QUIC流量混淆(如果你的网络屏蔽了 QUIC 或 HTTP/3 流量,请选择开启)?(Y/N,默认为N): " choice + choice="${choice:-N}" + if [[ "$choice" == "y" || "$choice" == "Y" ]]; then + read -p "请输入混淆密码(默认随机生成): " new_obfs_password + if [[ -z "$new_obfs_password" ]]; then + new_obfs_password=$(sing-box generate rand --base64 9 2>/dev/null || openssl rand -base64 9) + fi + obfs_config="\n \"obfs\": {\n \"type\": \"salamander\",\n \"password\": \"$new_obfs_password\"\n }," + obfs_password="$new_obfs_password" + echo "混淆密码:$obfs_password" + break + elif [[ "$choice" == "n" || "$choice" == "N" ]]; then + obfs_config="" + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function configure_obfuscation() { + while true; do + read -p "是否开启 obfs 混淆(用来绕过针对性的 DPI 屏蔽或者 QoS)?(Y/N,默认为N): " choice + choice="${choice:-N}" + if [[ "$choice" == "y" || "$choice" == "Y" ]]; then + read -p "请输入混淆密码(默认随机生成): " new_obfs_password + if [[ -z "$new_obfs_password" ]]; then + new_obfs_password=$(sing-box generate rand --base64 9 2>/dev/null || openssl rand -base64 9) + fi + obfs_config="\n \"obfs\": \"$new_obfs_password\"," + obfs_password="$new_obfs_password" + echo "混淆密码:$obfs_password" + break + elif [[ "$choice" == "n" || "$choice" == "N" ]]; then + obfs_config="" + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function configure_multiplex() { + while true; do + read -p "是否开启多路复用?(Y/N,默认为Y): " choice + choice="${choice:-Y}" + if [[ "$choice" == "y" || "$choice" == "Y" ]]; then + configure_brutal + multiplex_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"padding\": false$brutal_config\n }" + break + elif [[ "$choice" == "n" || "$choice" == "N" ]]; then + multiplex_config="" + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function configure_brutal() { + while true; do + read -p "是否开启 TCP Brutal?(Y/N,默认为N): " choice + choice="${choice:-N}" + if [[ "$choice" == "y" || "$choice" == "Y" ]]; then + set_up_speed + set_down_speed + brutal_config=",\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $up_mbps,\n \"down_mbps\": $down_mbps\n }" + break + elif [[ "$choice" == "n" || "$choice" == "N" ]]; then + brutal_config="" + break + else + echo -e "${RED}无效的输入,请重新输入!${NC}" + fi + done +} + +function extract_tls_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local tls_info_file="/usr/local/etc/sing-box/tls_info.json" + jq '.inbounds[].tls | select(.server_name and .certificate_path and .key_path) | {server_name: .server_name, certificate_path: .certificate_path, key_path: .key_path}' "$config_file" | jq -s 'unique' > "$tls_info_file" +} + +function validate_tls_info() { + local tls_info_file="/usr/local/etc/sing-box/tls_info.json" + local temp_tls_file="/usr/local/etc/sing-box/temp_tls_info.json" + server_names=($(jq -r '.[].server_name' "$tls_info_file")) + for server_name in "${server_names[@]}"; do + local resolved_ipv4=$(dig +short A "$server_name" 2>/dev/null) + local resolved_ipv6=$(dig +short AAAA "$server_name" 2>/dev/null) + if [[ (-n "$resolved_ipv4" && "$resolved_ipv4" == "$ip_v4") || (-n "$resolved_ipv6" && "$resolved_ipv6" == "$ip_v6") ]]; then + continue + else + jq 'map(select(.server_name != "'"$server_name"'"))' "$tls_info_file" > "$temp_tls_file" + mv "$temp_tls_file" "$tls_info_file" + fi + done +} + +function modify_route_rules() { + local config_file="/usr/local/etc/sing-box/config.json" + local temp_config_file="/usr/local/etc/sing-box/temp_config.json" + if jq -e '.route.rules[] | select(.geosite != null)' "$config_file" >/dev/null; then + jq '(.route.rules |= [.[] | select(.geosite != null)] + [.[] | select(.geosite == null)])' "$config_file" > "$temp_config_file" + mv "$temp_config_file" "$config_file" + fi +} + +function extract_variables_and_cleanup() { + server=$(jq -r '.server' "$temp_file") + server_port=$(jq -r '.server_port' "$temp_file") + local_address_ipv4=$(jq -r '.local_address[0]' "$temp_file") + local_address_ipv6=$(jq -r '.local_address[1]' "$temp_file") + private_key=$(jq -r '.private_key' "$temp_file") + peer_public_key=$(jq -r '.peer_public_key' "$temp_file") + reserved=$(jq -c '.reserved' "$temp_file") + mtu=$(jq -r '.mtu' "$temp_file") + rm "$temp_file" +} + +function log_outbound_config() { + local config_file="/usr/local/etc/sing-box/config.json" + if ! grep -q '"log": {' "$config_file" || ! grep -q '"route": {' "$config_file" || ! grep -q '"inbounds": \[' "$config_file" || ! grep -q '"outbounds": \[' "$config_file"; then + echo -e '{\n "log": {\n },\n "route": {\n },\n "inbounds": [\n ],\n "outbounds": [\n ]\n}' > "$config_file" + sed -i '/"log": {/!b;n;c\ "disabled": true,\n "level": "info",\n "timestamp": true\n },' "$config_file" + sed -i '/"route": {/!b;n;c\ "rules": [\n ]\n },' "$config_file" + sed -i '/"outbounds": \[/!b;n;c\ {\n "type": "direct",\n "tag": "direct"\n }\n ]' "$config_file" + fi +} + +function modify_format_inbounds_and_outbounds() { + file_path="/usr/local/etc/sing-box/config.json" + start_line_inbounds=$(grep -n '"inbounds": \[' "$file_path" | cut -d: -f1) + start_line_outbounds=$(grep -n '"outbounds": \[' "$file_path" | cut -d: -f1) + if [ -n "$start_line_inbounds" ]; then + line_to_modify_inbounds=$((start_line_inbounds - 3)) + if [ "$line_to_modify_inbounds" -ge 1 ]; then + sed -i "$line_to_modify_inbounds s/,//" "$file_path" + fi + fi + if [ -n "$start_line_outbounds" ]; then + line_to_modify_outbounds_1=$((start_line_outbounds - 2)) + line_to_modify_outbounds_2=$((start_line_outbounds - 1)) + if [ "$line_to_modify_outbounds_1" -ge 1 ]; then + sed -i "$line_to_modify_outbounds_1 s/.*/ }/" "$file_path" + sed -i "$line_to_modify_outbounds_2 s/.*/ ],/" "$file_path" + fi + fi +} + +function generate_http_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local cert_path="$certificate_path" + local key_path="$private_key_path" + tls_enabled=true + local tag_label + generate_unique_tag + set_listen_port + socks_naive_multiple_users + get_local_ip + generate_tls_config + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v tls_config="$tls_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"http\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"set_system_proxy\": false,"; print " \"users\": [" users ""; print " ]" tls_config ""; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_Direct_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v target_address="$target_address" -v override_port="$override_port" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"direct\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"sniff_timeout\": \"300ms\","; print " \"proxy_protocol\": false,"; print " \"override_address\": \"" target_address "\","; print " \"override_port\": " override_port; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_ss_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + configure_multiplex + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v ss_method="$ss_method" -v ss_password="$ss_password" -v multiplex_config="$multiplex_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"method\": \"" ss_method "\","; print " \"password\": \"" ss_password "\"" multiplex_config ""; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_vmess_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local cert_path="$certificate_path" + local key_path="$private_key_path" + local tag_label + generate_unique_tag + select_vmess_type + set_listen_port + vmess_multiple_users + generate_transport_config + if [ "$transport_grpc" != true ] && [ "$transport_http" != true ]; then + configure_multiplex + fi + get_local_ip + generate_tls_config + check_firewall_configuration + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v transport_config="$transport_config" -v tls_config="$tls_config" -v multiplex_config="$multiplex_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"vmess\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true," transport_config ""; print " \"users\": [" users ""; print " ]" tls_config "" multiplex_config ""; print " },"; found=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_socks_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + set_listen_port + socks_naive_multiple_users + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"socks\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"users\": [" users ""; print " ]"; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_naive_config() { + local config_file="/usr/local/etc/sing-box/config.json" + disable_option=true + local tag_label + generate_unique_tag + set_listen_port + socks_naive_multiple_users + get_local_ip + select_certificate_option + local cert_path="$certificate_path" + local key_path="$private_key_path" + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v domain="$domain" -v certificate_path="$certificate_path" -v private_key_path="$private_key_path" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"naive\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"users\": [" users ""; print " ],"; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" domain "\","; print " \"certificate_path\": \"" certificate_path "\","; print " \"key_path\": \"" private_key_path "\""; print " }"; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_tuic_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + set_listen_port + tuic_multiple_users + select_congestion_control + get_local_ip + set_ech_config + select_certificate_option + local cert_path="$certificate_path" + local key_path="$private_key_path" + local found_rules=0 + local found_inbounds=0 + local server_name="$domain" + if [ -z "$domain" ]; then + server_name="$domain_name" + fi + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v congestion_control="$congestion_control" -v server_name="$server_name" -v certificate_path="$certificate_path" -v private_key_path="$private_key_path" -v ech_server_config="$ech_server_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"tuic\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"users\": [" users ""; print " ],"; print " \"congestion_control\": \"" congestion_control "\","; print " \"auth_timeout\": \"3s\","; print " \"zero_rtt_handshake\": false,"; print " \"heartbeat\": \"10s\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" server_name "\","; print " \"alpn\": ["; print " \"h3\""; print " ],"; print " \"certificate_path\": \"" certificate_path "\","; print " \"key_path\": \"" private_key_path "\"" ech_server_config ""; print " }"; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_Hysteria_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + set_listen_port + set_up_speed + set_down_speed + hysteria_multiple_users + configure_obfuscation + get_local_ip + set_ech_config + select_certificate_option + local cert_path="$certificate_path" + local key_path="$private_key_path" + local found_rules=0 + local found_inbounds=0 + local server_name="$domain" + if [ -z "$domain" ]; then + server_name="$domain_name" + fi + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v users="$users" -v server_name="$server_name" -v certificate_path="$certificate_path" -v private_key_path="$private_key_path" -v ech_server_config="$ech_server_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"hysteria\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"up_mbps\": " up_mbps ","; print " \"down_mbps\": " down_mbps ","obfs_config""; print " \"users\": [" users ""; print " ],"; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" server_name "\","; print " \"alpn\": ["; print " \"h3\""; print " ],"; print " \"certificate_path\": \"" certificate_path "\","; print " \"key_path\": \"" private_key_path "\"" ech_server_config ""; print " }"; print " },"; found_inbounds=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_shadowtls_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + tag_label1="$tag_label" + generate_unique_tag + tag_label2="$tag_label" + set_listen_port + select_encryption_method + shadowtls_multiple_users + set_ss_password + set_target_server + configure_multiplex + local found_rules=0 + local found_inbounds=0 + awk -v tag_label1="$tag_label1" -v tag_label2="$tag_label2" -v listen_port="$listen_port" -v users="$users" -v target_server="$target_server" -v ss_method="$ss_method" -v ss_password="$ss_password" -v multiplex_config="$multiplex_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label1 "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"shadowtls\","; print " \"tag\": \"" tag_label1 "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"version\": 3,"; print " \"users\": [" users ""; print " ],"; print " \"handshake\": {"; print " \"server\": \"" target_server "\","; print " \"server_port\": 443"; print " },"; print " \"strict_mode\": true,"; print " \"detour\": \"" tag_label2 "\""; print " },"; print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" tag_label2 "\","; print " \"listen\": \"127.0.0.1\","; print " \"method\": \"" ss_method "\","; print " \"password\": \"" ss_password "\"" multiplex_config ""; print " },"; found=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_juicity_config() { + local config_file="/usr/local/etc/juicity/config.json" + set_listen_port + set_uuid + set_user_password + select_congestion_control + get_local_ip + select_certificate_option + local cert_path="$certificate_path" + local key_path="$private_key_path" + awk -v listen_port="$listen_port" -v user_uuids="$user_uuids" -v user_passwords="$user_passwords" -v certificate_path="$certificate_path" -v private_key_path="$private_key_path" -v congestion_control="$congestion_control" 'BEGIN { print "{"; printf " \"listen\": \":%s\",\n", listen_port; printf " \"users\": {\n"; printf " \"%s\": \"%s\"\n", user_uuids, user_passwords; printf " },\n"; printf " \"certificate\": \"%s\",\n", certificate_path; printf " \"private_key\": \"%s\",\n", private_key_path; printf " \"congestion_control\": \"%s\",\n", congestion_control; printf " \"disable_outbound_udp443\": true,\n"; print " \"log_level\": \"info\""; print "}"}' > "$config_file" +} + +function generate_vless_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + select_vless_type + set_listen_port + vless_multiple_users + generate_transport_config + generate_reality_config + if [[ "$flow_type" != xtls-rprx-vision ]] && [[ "$transport_grpc" != true ]] && [[ "$transport_http" != true ]]; then + configure_multiplex + fi + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v transport_config="$transport_config" -v reality_config="$reality_config" -v multiplex_config="$multiplex_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"vless\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true," transport_config ""; print " \"users\": [" users ""; print " ]"reality_config"" multiplex_config ""; print " },"; found=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_Hy2_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + set_listen_port + set_up_speed + set_down_speed + hy2_multiple_users + configure_quic_obfuscation + set_fake_domain + get_local_ip + set_ech_config + select_certificate_option + local cert_path="$certificate_path" + local key_path="$private_key_path" + local found_rules=0 + local found_inbounds=0 + local server_name="$domain" + if [ -z "$domain" ]; then + server_name="$domain_name" + fi + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v users="$users" -v fake_domain="$fake_domain" -v server_name="$server_name" -v certificate_path="$certificate_path" -v private_key_path="$private_key_path" -v ech_server_config="$ech_server_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"hysteria2\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true,"; print " \"up_mbps\": " up_mbps ","; print " \"down_mbps\": " down_mbps ","obfs_config""; print " \"users\": [" users ""; print " ],"; print " \"ignore_client_bandwidth\": false,"; print " \"masquerade\": \"https://" fake_domain "\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" server_name "\","; print " \"alpn\": ["; print " \"h3\""; print " ],"; print " \"certificate_path\": \"" certificate_path "\","; print " \"key_path\": \"" private_key_path "\"" ech_server_config ""; print " }"; print " },"; found=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function generate_trojan_config() { + local config_file="/usr/local/etc/sing-box/config.json" + local tag_label + generate_unique_tag + select_trojan_type + set_listen_port + trojan_multiple_users + generate_transport_config + if [ "$transport_grpc" != true ] && [ "$transport_http" != true ]; then + configure_multiplex + fi + get_local_ip + generate_tls_config + local cert_path="$certificate_path" + local key_path="$private_key_path" + check_firewall_configuration + local found_rules=0 + local found_inbounds=0 + awk -v tag_label="$tag_label" -v listen_port="$listen_port" -v users="$users" -v transport_config="$transport_config" -v tls_config="$tls_config" -v multiplex_config="$multiplex_config" ' + /"rules": \[/{found_rules=1} + /"inbounds": \[/{found_inbounds=1} + {print} + found_rules && /"rules": \[/{print " {"; print " \"inbound\": [\"" tag_label "\"],"; print " \"outbound\": \"direct\""; print " },"; found_rules=0} + found_inbounds && /"inbounds": \[/{print " {"; print " \"type\": \"trojan\","; print " \"tag\": \"" tag_label "\","; print " \"listen\": \"::\","; print " \"listen_port\": " listen_port ","; print " \"sniff\": true,"; print " \"sniff_override_destination\": true," transport_config ""; print " \"users\": [" users ""; print " ]" tls_config "" multiplex_config ""; print " },"; found=0} + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" +} + +function update_route_file() { + local config_file="/usr/local/etc/sing-box/config.json" + local geosite_list=$(IFS=,; echo "${geosite[*]}") + local geosite_formatted=$(sed 's/,/,\\n /g' <<< "$geosite_list") + echo "正在配置 WireGuard..." + sed -i '/"rules": \[/!b;a\ + {\ + "geosite": [\ + '"$geosite_formatted"'\ + ],\ + "outbound": "'"$1"'"\ + },' "$config_file" +} + +function update_outbound_file() { + local config_file="/usr/local/etc/sing-box/config.json" + awk -v server="$server" -v server_port="$server_port" -v local_address_ipv4="$local_address_ipv4" -v local_address_ipv6="$local_address_ipv6" -v private_key="$private_key" -v peer_public_key="$peer_public_key" -v reserved="$reserved" -v mtu="$mtu" ' + { + if ($0 ~ /"outbounds": \[/) { + print $0 + for (i=1; i<=4; i++) { + getline + if (i == 4) { + print "" $0 "," + } else { + print $0 + } + } + print " {"; print " \"type\": \"direct\","; print " \"tag\": \"warp-IPv4-out\","; print " \"detour\": \"wireguard-out\","; print " \"domain_strategy\": \"ipv4_only\""; print " },"; print " {"; print " \"type\": \"direct\","; print " \"tag\": \"warp-IPv6-out\","; print " \"detour\": \"wireguard-out\","; print " \"domain_strategy\": \"ipv6_only\""; print " },"; print " {"; print " \"type\": \"wireguard\","; print " \"tag\": \"wireguard-out\","; print " \"server\": \"" server "\","; print " \"server_port\": " server_port ","; print " \"system_interface\": false,"; print " \"interface_name\": \"wg0\","; print " \"local_address\": ["; print " \"" local_address_ipv4 "\","; print " \"" local_address_ipv6 "\"" ; print " ],"; print " \"private_key\": \"" private_key "\","; print " \"peer_public_key\": \"" peer_public_key "\","; print " \"reserved\": " reserved ","; print " \"mtu\": " mtu; print " }" + } else { + print $0 + } + } + ' "$config_file" > "$config_file.tmp" + mv "$config_file.tmp" "$config_file" + echo "WireGuard 配置完成。" +} + +function write_phone_client_file() { + local dir="/usr/local/etc/sing-box" + local phone_client="${dir}/phone_client.json" + if [ ! -s "${phone_client}" ]; then + awk 'BEGIN { print "{"; print " \"log\": {"; print " \"disabled\": false,"; print " \"level\": \"warn\","; print " \"timestamp\": true"; print " },"; print " \"dns\": {"; print " \"servers\": ["; print " {"; print " \"tag\": \"dns_proxy\","; print " \"address\": \"https://dns.google/dns-query\","; print " \"address_resolver\": \"dns_local\","; print " \"detour\": \"select\""; print " },"; print " {"; print " \"tag\": \"dns_direct\","; print " \"address\": \"https://dns.alidns.com/dns-query\","; print " \"address_resolver\": \"dns_local\","; print " \"detour\": \"direct\""; print " },"; print " {"; print " \"tag\": \"dns_block\","; print " \"address\": \"rcode://success\""; print " },"; print " {"; print " \"tag\": \"dns_fakeip\","; print " \"address\": \"fakeip\""; print " },"; print " {"; print " \"tag\": \"dns_local\","; print " \"address\": \"223.5.5.5\","; print " \"detour\": \"direct\""; print " }"; print " ],"; print " \"rules\": ["; print " {"; print " \"outbound\": \"any\","; print " \"server\": \"dns_local\""; print " },"; print " {"; print " \"geosite\": ["; print " \"category-ads-all\""; print " ],"; print " \"server\": \"dns_block\","; print " \"disable_cache\": true"; print " },"; print " {"; print " \"query_type\": ["; print " \"A\","; print " \"AAAA\""; print " ],"; print " \"server\": \"dns_fakeip\""; print " },"; print " {"; print " \"clash_mode\": \"Direct\","; print " \"server\": \"dns_direct\""; print " },"; print " {"; print " \"clash_mode\": \"Global\","; print " \"server\": \"dns_proxy\""; print " },"; print " {"; print " \"type\": \"logical\","; print " \"mode\": \"and\","; print " \"rules\": ["; print " {"; print " \"geosite\": \"geolocation-!cn\","; print " \"invert\": true"; print " },"; print " {"; print " \"geosite\": ["; print " \"cn\","; print " \"category-companies@cn\""; print " ]"; print " }"; print " ],"; print " \"server\": \"dns_direct\""; print " }"; print " ],"; print " \"final\": \"dns_proxy\","; print " \"strategy\": \"ipv4_only\","; print " \"independent_cache\": true,"; print " \"fakeip\": {"; print " \"enabled\": true,"; print " \"inet4_range\": \"198.18.0.0/15\","; print " \"inet6_range\": \"fc00::/18\""; print " }"; print " },"; print " \"route\": {"; print " \"geoip\": {"; print " \"download_url\": \"https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db\","; print " \"download_detour\": \"select\""; print " },"; print " \"geosite\": {"; print " \"download_url\": \"https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db\","; print " \"download_detour\": \"select\""; print " },"; print " \"rules\": ["; print " {"; print " \"protocol\": \"dns\","; print " \"outbound\": \"dns-out\""; print " },"; print " {"; print " \"geoip\": \"private\","; print " \"outbound\": \"direct\""; print " },"; print " {"; print " \"clash_mode\": \"Direct\","; print " \"outbound\": \"direct\""; print " },"; print " {"; print " \"clash_mode\": \"Global\","; print " \"outbound\": \"select\""; print " },"; print " {"; print " \"type\": \"logical\","; print " \"mode\": \"and\","; print " \"rules\": ["; print " {"; print " \"geosite\": \"geolocation-!cn\","; print " \"invert\": true"; print " },"; print " {"; print " \"geosite\": ["; print " \"cn\","; print " \"category-companies@cn\""; print " ],"; print " \"geoip\": \"cn\""; print " }"; print " ],"; print " \"outbound\": \"direct\""; print " }"; print " ],"; print " \"final\": \"select\","; print " \"auto_detect_interface\": true"; print " },"; print " \"inbounds\": ["; print " {"; print " \"type\": \"tun\","; print " \"tag\": \"tun-in\","; print " \"inet4_address\": \"172.19.0.1/30\","; print " \"inet6_address\": \"fdfe:dcba:9876::1/126\","; print " \"auto_route\": true,"; print " \"strict_route\": true,"; print " \"stack\": \"mixed\","; print " \"sniff\": true,"; print " \"sniff_override_destination\": false"; print " }"; print " ],"; print " \"outbounds\": ["; print " {"; print " \"type\": \"urltest\","; print " \"tag\": \"auto\","; print " \"outbounds\": ["; print " ],"; print " \"url\": \"https://www.gstatic.com/generate_204\","; print " \"interval\": \"1m\","; print " \"tolerance\": 50,"; print " \"interrupt_exist_connections\": false"; print " },"; print " {"; print " \"type\": \"selector\","; print " \"tag\": \"select\","; print " \"outbounds\": ["; print " \"auto\""; print " ],"; print " \"default\": \"auto\","; print " \"interrupt_exist_connections\": false"; print " },"; print " {"; print " \"type\": \"direct\","; print " \"tag\": \"direct\""; print " },"; print " {"; print " \"type\": \"block\","; print " \"tag\": \"block\""; print " },"; print " {"; print " \"type\": \"dns\","; print " \"tag\": \"dns-out\""; print " }"; print " ],"; print " \"experimental\": {"; print " \"clash_api\": {"; print " \"external_controller\": \"127.0.0.1:9090\","; print " \"external_ui\": \"ui\","; print " \"external_ui_download_detour\": \"direct\","; print " \"store_fakeip\": true"; print " }"; print " },"; print " \"ntp\": {"; print " \"enabled\": true,"; print " \"server\": \"time.apple.com\","; print " \"server_port\": 123,"; print " \"interval\": \"30m\","; print " \"detour\": \"direct\""; print " }"; print "}" }' > "${phone_client}" + fi +} + +function write_win_client_file() { + local dir="/usr/local/etc/sing-box" + local win_client="${dir}/win_client.json" + if [ ! -s "${win_client}" ]; then + awk 'BEGIN { print "{"; print " \"log\": {"; print " \"disabled\": false,"; print " \"level\": \"warn\","; print " \"timestamp\": true"; print " },"; print " \"dns\": {"; print " \"servers\": ["; print " {"; print " \"tag\": \"dns_proxy\","; print " \"address\": \"https://dns.google/dns-query\","; print " \"address_resolver\": \"dns_local\","; print " \"detour\": \"select\""; print " },"; print " {"; print " \"tag\": \"dns_direct\","; print " \"address\": \"https://dns.alidns.com/dns-query\","; print " \"address_resolver\": \"dns_local\","; print " \"detour\": \"direct\""; print " },"; print " {"; print " \"tag\": \"dns_block\","; print " \"address\": \"rcode://success\""; print " },"; print " {"; print " \"tag\": \"dns_fakeip\","; print " \"address\": \"fakeip\""; print " },"; print " {"; print " \"tag\": \"dns_local\","; print " \"address\": \"223.5.5.5\","; print " \"detour\": \"direct\""; print " }"; print " ],"; print " \"rules\": ["; print " {"; print " \"outbound\": \"any\","; print " \"server\": \"dns_local\""; print " },"; print " {"; print " \"geosite\": ["; print " \"category-ads-all\""; print " ],"; print " \"server\": \"dns_block\","; print " \"disable_cache\": true"; print " },"; print " {"; print " \"query_type\": ["; print " \"A\","; print " \"AAAA\""; print " ],"; print " \"server\": \"dns_fakeip\""; print " },"; print " {"; print " \"clash_mode\": \"Direct\","; print " \"server\": \"dns_direct\""; print " },"; print " {"; print " \"clash_mode\": \"Global\","; print " \"server\": \"dns_proxy\""; print " },"; print " {"; print " \"type\": \"logical\","; print " \"mode\": \"and\","; print " \"rules\": ["; print " {"; print " \"geosite\": \"geolocation-!cn\","; print " \"invert\": true"; print " },"; print " {"; print " \"geosite\": ["; print " \"cn\","; print " \"category-companies@cn\""; print " ]"; print " }"; print " ],"; print " \"server\": \"dns_direct\""; print " }"; print " ],"; print " \"final\": \"dns_proxy\","; print " \"strategy\": \"ipv4_only\","; print " \"independent_cache\": true,"; print " \"fakeip\": {"; print " \"enabled\": true,"; print " \"inet4_range\": \"198.18.0.0/15\","; print " \"inet6_range\": \"fc00::/18\""; print " }"; print " },"; print " \"route\": {"; print " \"geoip\": {"; print " \"download_url\": \"https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db\","; print " \"download_detour\": \"select\""; print " },"; print " \"geosite\": {"; print " \"download_url\": \"https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db\","; print " \"download_detour\": \"select\""; print " },"; print " \"rules\": ["; print " {"; print " \"protocol\": \"dns\","; print " \"outbound\": \"dns-out\""; print " },"; print " {"; print " \"geoip\": \"private\","; print " \"outbound\": \"direct\""; print " },"; print " {"; print " \"clash_mode\": \"Direct\","; print " \"outbound\": \"direct\""; print " },"; print " {"; print " \"clash_mode\": \"Global\","; print " \"outbound\": \"select\""; print " },"; print " {"; print " \"type\": \"logical\","; print " \"mode\": \"and\","; print " \"rules\": ["; print " {"; print " \"geosite\": \"geolocation-!cn\","; print " \"invert\": true"; print " },"; print " {"; print " \"geosite\": ["; print " \"cn\","; print " \"category-companies@cn\""; print " ],"; print " \"geoip\": \"cn\""; print " }"; print " ],"; print " \"outbound\": \"direct\""; print " }"; print " ],"; print " \"final\": \"select\","; print " \"auto_detect_interface\": true"; print " },"; print " \"inbounds\": ["; print " {"; print " \"type\": \"mixed\","; print " \"tag\": \"mixed-in\","; print " \"listen\": \"::\","; print " \"listen_port\": 1080,"; print " \"sniff\": true,"; print " \"set_system_proxy\": false"; print " }"; print " ],"; print " \"outbounds\": ["; print " {"; print " \"type\": \"urltest\","; print " \"tag\": \"auto\","; print " \"outbounds\": ["; print " ],"; print " \"url\": \"https://www.gstatic.com/generate_204\","; print " \"interval\": \"1m\","; print " \"tolerance\": 50,"; print " \"interrupt_exist_connections\": false"; print " },"; print " {"; print " \"type\": \"selector\","; print " \"tag\": \"select\","; print " \"outbounds\": ["; print " \"auto\""; print " ],"; print " \"default\": \"auto\","; print " \"interrupt_exist_connections\": false"; print " },"; print " {"; print " \"type\": \"direct\","; print " \"tag\": \"direct\""; print " },"; print " {"; print " \"type\": \"block\","; print " \"tag\": \"block\""; print " },"; print " {"; print " \"type\": \"dns\","; print " \"tag\": \"dns-out\""; print " }"; print " ],"; print " \"experimental\": {"; print " \"clash_api\": {"; print " \"external_controller\": \"127.0.0.1:9090\","; print " \"external_ui\": \"ui\","; print " \"external_ui_download_detour\": \"direct\","; print " \"store_fakeip\": true"; print " }"; print " },"; print " \"ntp\": {"; print " \"enabled\": true,"; print " \"server\": \"time.apple.com\","; print " \"server_port\": 123,"; print " \"interval\": \"30m\","; print " \"detour\": \"direct\""; print " }"; print "}" }' > "${win_client}" + fi +} + +function write_clash_yaml() { + local dir="/usr/local/etc/sing-box" + local clash_yaml="${dir}/clash.yaml" + if [ ! -s "${clash_yaml}" ]; then + awk 'BEGIN { print "mixed-port: 10801"; print "allow-lan: true"; print "bind-address: \"*\""; print "find-process-mode: strict"; print "mode: rule"; print "geodata-mode: true"; print "geox-url:"; print " geoip: \"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat\""; print " geosite: \"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat\""; print " mmdb: \"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb\""; print "log-level: info"; print "ipv6: true"; print "global-client-fingerprint: chrome"; print "tun:"; print " enable: true"; print " stack: system"; print " dns-hijack:"; print " - 0.0.0.0:53"; print " auto-detect-interface: true"; print " auto-route: true"; print " mtu: 9000"; print "profile:"; print " store-selected: false"; print " store-fake-ip: true"; print "sniffer:"; print " enable: true"; print " override-destination: false"; print " sniff:"; print " TLS:"; print " ports: [443, 8443]"; print " HTTP:"; print " ports: [80, 8080-8880]"; print " override-destination: true"; print "dns:"; print " enable: true"; print " prefer-h3: true"; print " listen: 0.0.0.0:53"; print " ipv6: true"; print " ipv6-timeout: 300"; print " default-nameserver:"; print " - 223.5.5.5"; print " enhanced-mode: fake-ip"; print " fake-ip-range: 198.18.0.1/16"; print " nameserver:"; print " - https://doh.pub/dns-query"; print " - https://dns.alidns.com/dns-query"; print " fallback:"; print " - https://dns.google/dns-query"; print " - https://1.1.1.1/dns-query"; print " fallback-filter:"; print " geoip: true"; print " geoip-code: CN"; print " geosite:"; print " - gfw"; print " ipcidr:"; print " - 240.0.0.0/4"; print " domain:"; print " - \"+.google.com\""; print " - \"+.facebook.com\""; print " - \"+.youtube.com\""; print " nameserver-policy:"; print " \"geosite:cn,private\":"; print " - https://doh.pub/dns-query"; print " - https://dns.alidns.com/dns-query"; print " \"geosite:category-ads-all\": rcode://success"; print "proxies:"; print "proxy-groups:"; print " - name: Proxy"; print " type: select"; print " proxies:"; print " - auto"; print " - name: auto"; print " type: url-test"; print " proxies:"; print " url: \"https://cp.cloudflare.com/generate_204\""; print " interval: 300"; print "rules:"; print " - GEOSITE,private,DIRECT"; print " - GEOSITE,category-ads-all,REJECT"; print " - GEOSITE,cn,DIRECT"; print " - GEOIP,cn,DIRECT"; print " - MATCH,Proxy"; }' > "${clash_yaml}" + sed -i'' -e '/^ - "+\.google\.com"/s/"/'\''/g' "${clash_yaml}" + sed -i'' -e '/^ - "+\.facebook\.com"/s/"/'\''/g' "${clash_yaml}" + sed -i'' -e '/^ - "+\.youtube\.com"/s/"/'\''/g' "${clash_yaml}" + fi +} + +function write_naive_client_file() { + local naive_client_file="$naive_client_filename" + awk -v naive_client_file="$naive_client_file" 'BEGIN { print "{"; print " \"listen\": \"socks://127.0.0.1:1080\","; print " \"proxy\": \"https://user_name:user_password@server_name:listen_port\""; print "}" }' > "$naive_client_file" +} + +function generate_shadowsocks_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="ss-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v ss_method="$ss_method" -v ss_password="$ss_password" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"method\": \"" ss_method "\", "; print " \"password\": \"" ss_password "\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_shadowsocks_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="ss-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v ss_method="$ss_method" -v ss_password="$ss_password" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"method\": \"" ss_method "\", "; print " \"password\": \"" ss_password "\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_shadowsocks_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="ss-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v ss_method="$ss_method" -v ss_password="$ss_password" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: ss"; print " server:", local_ip; print " port:", listen_port; print " cipher:", ss_method; print " password:", "\"" ss_password "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_juicity_win_client_config() { + local client_file="/usr/local/etc/juicity/client.json" + local server_name="$domain" + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + awk -v listen_port="$listen_port" -v server_value="$server_value" -v user_uuids="$user_uuids" -v user_passwords="$user_passwords" -v server_name="$server_name" -v tls_insecure="$tls_insecure" -v congestion_control="$congestion_control" 'BEGIN { print "{"; printf " \"listen\": \":%s\",\n", 1080; printf " \"server\": \"%s:%s\",\n", server_value, listen_port; printf " \"uuid\": \"%s\",\n", user_uuids; printf " \"password\": \"%s\",\n", user_passwords; printf " \"sni\": \"%s\",\n", server_name; printf " \"allow_insecure\": %s,\n", tls_insecure; printf " \"congestion_control\": \"%s\",\n", congestion_control; printf " \"log_level\": \"info\"\n"; print "}"}' > "$client_file" + echo "客户端配置文件已保存至$client_file,请下载后使用!" +} + +function generate_tuic_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="tuic-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v user_password="$user_password" -v congestion_control="$congestion_control" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"tuic\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\", "; print " \"password\": \"" user_password "\", "; print " \"congestion_control\": \""congestion_control"\","; print " \"udp_relay_mode\": \"native\","; print " \"zero_rtt_handshake\": false,"; print " \"heartbeat\": \"10s\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_tuic_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="tuic-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v user_password="$user_password" -v congestion_control="$congestion_control" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"tuic\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\", "; print " \"password\": \"" user_password "\", "; print " \"congestion_control\": \""congestion_control"\","; print " \"udp_relay_mode\": \"native\","; print " \"zero_rtt_handshake\": false,"; print " \"heartbeat\": \"10s\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_tuic_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="tuic-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v user_password="$user_password" -v congestion_control="$congestion_control" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " server:", server_value; print " port:", listen_port; print " type: tuic"; print " uuid:", user_uuid; print " password:", user_password; print " sni:", server_name; print " alpn: [h3]"; print " request-timeout: 8000"; print " udp-relay-mode: native"; print " skip-cert-verify:", tls_insecure; print " congestion-controller:", congestion_control; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_socks_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + while true; do + proxy_name="socks-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"socks\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"username\": \"" user_name "\", "; print " \"password\": \"" user_password "\" "; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_socks_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + while true; do + proxy_name="socks-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"socks\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"username\": \"" user_name "\", "; print " \"password\": \"" user_password "\" "; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_socks_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="socks-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: socks5"; print " server:", local_ip; print " port:", listen_port; print " username:", user_name; print " password:", user_password; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_Hysteria_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config="\n \"obfs\": \"$obfs_password\"," + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="Hysteria-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"hysteria\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"up_mbps\": " down_mbps ", "; print " \"down_mbps\": " up_mbps ","obfs_config""; print " \"auth_str\": \""user_password"\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_Hysteria_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config="\n \"obfs\": \"$obfs_password\"," + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="Hysteria-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"hysteria\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"up_mbps\": " down_mbps ", "; print " \"down_mbps\": " up_mbps ","obfs_config""; print " \"auth_str\": \""user_password"\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_Hysteria_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config=" + obfs: $obfs_password" + fi + + while true; do + proxy_name="hysteria-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v user_password="$user_password" -v obfs_config="$obfs_config" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: hysteria"; print " server:", server_value; print " port:", listen_port; print " auth-str:", user_password obfs_config; print " sni:", server_name; print " skip-cert-verify:", tls_insecure; print " alpn:"; print " - h3"; print " protocol: udp"; print " up: \"" down_mbps " Mbps\""; print " down: \"" up_mbps " Mbps\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + local server_name="$domain" + local server_value + local tls_insecure + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_value="$local_ip" + elif [[ -z "$domain_name" && -n "$domain" ]]; then + server_name="$domain" + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="vmess-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + if [[ -n "$domain" || -n "$domain_name" ]]; then + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_config="$transport_config" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vmess\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config " "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " },"; print " \"security\": \"auto\","; print " \"alter_id\": 0,"; print " \"packet_encoding\": \"xudp\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vmess\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config " "; print " \"security\": \"auto\","; print " \"alter_id\": 0,"; print " \"packet_encoding\": \"xudp\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + fi + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_vmess_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + local server_name="$domain" + local server_value + local tls_insecure + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_value="$local_ip" + elif [[ -z "$domain_name" && -n "$domain" ]]; then + server_name="$domain" + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="vmess-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + if [[ -n "$domain" || -n "$domain_name" ]]; then + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_config="$transport_config" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vmess\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config " "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " },"; print " \"security\": \"auto\","; print " \"alter_id\": 0,"; print " \"packet_encoding\": \"xudp\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vmess\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config " "; print " \"security\": \"auto\","; print " \"alter_id\": 0,"; print " \"packet_encoding\": \"xudp\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + fi + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_vmess_tcp_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vmess-tcp-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_tcp_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="vmess-tcp-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", server_value; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print " tls: true"; print " skip-cert-verify:", tls_insecure; print " servername: " server_name; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_ws_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vmess-ws-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_path="$transport_path" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print " network: ws"; print " ws-opts:"; print " path: " transport_path; print " max-early-data: 2048"; print " early-data-header-name: Sec-WebSocket-Protocol"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_ws_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="vmess-ws-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_path="$transport_path" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", server_value; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print " network: ws"; print " tls: true"; print " skip-cert-verify:", tls_insecure; print " servername:", server_name; print " ws-opts:"; print " path: " transport_path; print " max-early-data: 2048"; print " early-data-header-name: Sec-WebSocket-Protocol"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_grpc_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vmess-grpc-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_service_name="$transport_service_name" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print " network: grpc"; print " grpc-opts:"; print " grpc-service-name:", "\"" transport_service_name "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vmess_grpc_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="vmess-grpc-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_service_name="$transport_service_name" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vmess"; print " server:", server_value; print " port:", listen_port; print " uuid:", user_uuid; print " alterId: 0"; print " cipher: auto"; print " network: grpc"; print " tls: true"; print " skip-cert-verify:", tls_insecure; print " servername:", server_name; print " grpc-opts:"; print " grpc-service-name:", "\"" transport_service_name "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_http_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="http-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"http\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"username\": \"" user_name "\", "; print " \"password\": \"" user_password "\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_http_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="http-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"http\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"username\": \"" user_name "\", "; print " \"password\": \"" user_password "\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_http_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="http-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_name="$user_name" -v user_password="$user_password" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: http"; print " server:", server_value; print " port:", listen_port; print " username:", user_name; print " password:", user_password; print " tls: true"; print " sni:", server_name; print " skip-cert-verify:", tls_insecure; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_Hysteria2_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config="\n \"obfs\": {\n \"type\": \"salamander\",\n \"password\": \"$obfs_password\"\n }," + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="Hysteria2-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"hysteria2\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"up_mbps\": " down_mbps ", "; print " \"down_mbps\": " up_mbps ","obfs_config""; print " \"password\": \"" user_password "\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_Hysteria2_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config="\n \"obfs\": {\n \"type\": \"salamander\",\n \"password\": \"$obfs_password\"\n }," + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="Hysteria2-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v obfs_config="$obfs_config" -v user_password="$user_password" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"hysteria2\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"up_mbps\": " down_mbps ", "; print " \"down_mbps\": " up_mbps ","obfs_config""; print " \"password\": \"" user_password "\","; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\", "; print " \"alpn\": ["; print " \"h3\""; print " ]" ech_client_config ""; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_Hysteria2_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$obfs_password" ]; then + obfs_config=" + obfs: salamander + obfs-password: $obfs_password" + fi + while true; do + proxy_name="hysteria2-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v up_mbps="$up_mbps" -v down_mbps="$down_mbps" -v user_password="$user_password" -v obfs_config="$obfs_config" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: hysteria2"; print " server:", server_value; print " port:", listen_port; print " password:", user_password obfs_config; print " alpn:"; print " - h3"; print " sni:", server_name; print " skip-cert-verify:", tls_insecure; print " up: \"" down_mbps " Mbps\""; print " down: \"" up_mbps " Mbps\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vless_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + local server_name_in_config=$(jq -r '.inbounds[0].tls.server_name' "$config_file") + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="vless-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + if [ "$server_name_in_config" != "null" ]; then + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v flow_type="$flow_type" -v public_key="$public_key" -v short_id="$short_id" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vless\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\", "; print " \"flow\": \"" flow_type "\"," transport_config ""; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" server_name "\", "; print " \"utls\": {"; print " \"enabled\": true,"; print " \"fingerprint\": \"chrome\""; print " },"; print " \"reality\": {"; print " \"enabled\": true,"; print " \"public_key\": \"" public_key "\","; print " \"short_id\": \"" short_id "\""; print " }"; print " }" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v flow_type="$flow_type" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vless\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config ""; print " \"flow\": \"" flow_type "\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + fi + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_vless_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + local server_name_in_config=$(jq -r '.inbounds[0].tls.server_name' "$config_file") + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="vless-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + if [ "$server_name_in_config" != "null" ]; then + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v flow_type="$flow_type" -v public_key="$public_key" -v short_id="$short_id" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vless\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\", "; print " \"flow\": \"" flow_type "\"," transport_config ""; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" server_name "\", "; print " \"utls\": {"; print " \"enabled\": true,"; print " \"fingerprint\": \"chrome\""; print " },"; print " \"reality\": {"; print " \"enabled\": true,"; print " \"public_key\": \"" public_key "\","; print " \"short_id\": \"" short_id "\""; print " }"; print " }" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v flow_type="$flow_type" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"vless\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"uuid\": \"" user_uuid "\"," transport_config ""; print " \"flow\": \"" flow_type "\"" multiplex_client_config ""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + fi + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_vless_tcp_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vless-tcp-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vless"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " network: tcp"; print " udp: true"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vless_ws_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vless-ws-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_path="$transport_path" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vless"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " network: ws"; print " udp: true"; print " ws-opts:"; print " path: " transport_path; print " max-early-data: 2048"; print " early-data-header-name: Sec-WebSocket-Protocol"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vless_grpc_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vless-grpc-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v transport_service_name="$transport_service_name" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vless"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " network: grpc"; print " udp: true"; print " grpc-opts:"; print " grpc-service-name:", "\"" transport_service_name "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vless_reality_vision_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vless-reality-vision-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v public_key="$public_key" -v short_id="$short_id" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vless"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " network: tcp"; print " udp: true"; print " tls: true"; print " flow: xtls-rprx-vision"; print " servername:", server_name; print " reality-opts:"; print " public-key:", public_key; print " short-id:", short_id; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_vless_reality_grpc_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="vless-reality-grpc-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v server_name="$server_name" -v listen_port="$listen_port" -v user_uuid="$user_uuid" -v public_key="$public_key" -v short_id="$short_id" -v transport_service_name="$transport_service_name" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: vless"; print " server:", local_ip; print " port:", listen_port; print " uuid:", user_uuid; print " network: grpc"; print " udp: true"; print " tls: true"; print " flow: "; print " servername:", server_name; print " reality-opts:"; print " public-key:", public_key; print " short-id:", short_id; print " grpc-opts:"; print " grpc-service-name:", "\"" transport_service_name "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_trojan_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + local server_name="$domain" + local server_value + local tls_insecure + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_value="$local_ip" + elif [[ -z "$domain_name" && -n "$domain" ]]; then + server_name="$domain" + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="trojan-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file"; then + break + fi + done + if [[ -n "$domain" || -n "$domain_name" ]]; then + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_config="$transport_config" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"trojan\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"password\": \"" user_password "\"," transport_config " "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " }"multiplex_client_config""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"trojan\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port "," transport_config " "; print " \"password\": \"" user_password "\""multiplex_client_config""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + fi + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_trojan_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + local server_name="$domain" + local server_value + local tls_insecure + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_value="$local_ip" + elif [[ -z "$domain_name" && -n "$domain" ]]; then + server_name="$domain" + server_value="$domain" + tls_insecure="false" + fi + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + if [ -n "$ech_config" ]; then + ech_client_config=",\n \"ech\": {\n \"enabled\": true,\n \"pq_signature_schemes_enabled\": true,\n \"dynamic_record_sizing_disabled\": false,\n \"config\": [\n$ech_config\n ]\n }" + fi + while true; do + proxy_name="trojan-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file"; then + break + fi + done + if [[ -n "$domain" || -n "$domain_name" ]]; then + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_config="$transport_config" -v tls_insecure="$tls_insecure" -v ech_client_config="$ech_client_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"trojan\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" server_value "\", "; print " \"server_port\": " listen_port ","; print " \"password\": \"" user_password "\"," transport_config " "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"insecure\": " tls_insecure ","; print " \"server_name\": \"" server_name "\"" ech_client_config ""; print " }"multiplex_client_config""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + else + awk -v proxy_name="$proxy_name" -v local_ip="$local_ip" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_config="$transport_config" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"trojan\","; print " \"tag\": \"" proxy_name "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port "," transport_config " "; print " \"password\": \"" user_password "\""multiplex_client_config""; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + fi + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_trojan_tcp_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="trojan-tcp-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_password="$user_password" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: trojan"; print " server:", server_value; print " port:", listen_port; print " password:", user_password; print " udp: true"; print " sni:", server_name; print " skip-cert-verify:", tls_insecure; print " alpn:"; print " - h2"; print " - http/1.1"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_trojan_ws_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="trojan-ws-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_path="$transport_path" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: trojan"; print " server:", server_value; print " port:", listen_port; print " password:", "\"" user_password "\""; print " network: ws"; print " sni:", server_name; print " skip-cert-verify:", tls_insecure; print " udp: true"; print " ws-opts:"; print " path:", transport_path; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_trojan_grpc_tls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local server_name="$domain" + local proxy_name + local server_value + local tls_insecure + if [ -z "$domain" ]; then + server_name="$domain_name" + server_value="$local_ip" + tls_insecure="true" + else + server_value="$domain" + tls_insecure="false" + fi + while true; do + proxy_name="trojan-grpc-tls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v server_value="$server_value" -v server_name="$server_name" -v listen_port="$listen_port" -v user_password="$user_password" -v transport_service_name="$transport_service_name" -v tls_insecure="$tls_insecure" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: trojan"; print " server:", server_value; print " port:", listen_port; print " password:", "\"" user_password "\""; print " network: grpc"; print " sni:", server_name; print " udp: true"; print " skip-cert-verify:", tls_insecure; print " grpc-opts:"; print " grpc-service-name:", "\"" transport_service_name "\""; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_shadowtls_win_client_config() { + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local proxy_name + local shadowtls_out + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="shadowtls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + shadowtls_out="stl-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$win_client_file" && ! grep -q "name: $shadowtls_out" "$win_client_file" && [ "$proxy_name" != "$shadowtls_out" ]; then + break + fi + done + awk -v shadowtls_out="$shadowtls_out" -v proxy_name="$proxy_name" -v method="$method" -v ss_password="$ss_password" -v local_ip="$local_ip" -v listen_port="$listen_port" -v stls_password="$stls_password" -v user_input="$user_input" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" proxy_name "\","; print " \"method\": \"" method "\", "; print " \"password\": \"" ss_password "\","; print " \"detour\": \"" shadowtls_out "\""multiplex_client_config""; print " },"; print " {"; print " \"type\": \"shadowtls\","; print " \"tag\": \"" shadowtls_out "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"version\": 3, "; print " \"password\": \""stls_password"\", "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" user_input "\", "; print " \"utls\": {"; print " \"enabled\": true,"; print " \"fingerprint\": \"chrome\" "; print " }"; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" +} + +function generate_shadowtls_phone_client_config() { + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local proxy_name + local shadowtls_out + if [ -n "$multiplex_config" ] && [ -n "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false,\n \"brutal\": {\n \"enabled\": true,\n \"up_mbps\": $down_mbps,\n \"down_mbps\": $up_mbps\n }\n }" + elif [ -n "$multiplex_config" ] && [ -z "$brutal_config" ]; then + multiplex_client_config=",\n \"multiplex\": {\n \"enabled\": true,\n \"protocol\": \"h2mux\",\n \"max_connections\": 1,\n \"min_streams\": 4,\n \"padding\": false\n }" + fi + while true; do + proxy_name="shadowtls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + shadowtls_out="stl-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$phone_client_file" && ! grep -q "name: $shadowtls_out" "$phone_client_file" && [ "$proxy_name" != "$shadowtls_out" ]; then + break + fi + done + awk -v shadowtls_out="$shadowtls_out" -v proxy_name="$proxy_name" -v method="$method" -v ss_password="$ss_password" -v local_ip="$local_ip" -v listen_port="$listen_port" -v stls_password="$stls_password" -v user_input="$user_input" -v multiplex_client_config="$multiplex_client_config" ' + /^ "outbounds": \[/ {print; getline; print " {"; print " \"type\": \"shadowsocks\","; print " \"tag\": \"" proxy_name "\","; print " \"method\": \"" method "\", "; print " \"password\": \"" ss_password "\","; print " \"detour\": \"" shadowtls_out "\""multiplex_client_config""; print " },"; print " {"; print " \"type\": \"shadowtls\","; print " \"tag\": \"" shadowtls_out "\","; print " \"server\": \"" local_ip "\", "; print " \"server_port\": " listen_port ","; print " \"version\": 3, "; print " \"password\": \""stls_password"\", "; print " \"tls\": {"; print " \"enabled\": true,"; print " \"server_name\": \"" user_input "\", "; print " \"utls\": {"; print " \"enabled\": true,"; print " \"fingerprint\": \"chrome\" "; print " }"; print " }"; print " },";} + /^ "outbounds": \[/ {print; getline; if ($0 ~ /^ \],$/) {print " \"" proxy_name "\""} else {print " \"" proxy_name "\", "} } + {print}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" +} + +function generate_shadowtls_yaml() { + local filename="/usr/local/etc/sing-box/clash.yaml" + local proxy_name + while true; do + proxy_name="shadowtls-$(head /dev/urandom | tr -dc '0-9' | head -c 4)" + if ! grep -q "name: $proxy_name" "$filename"; then + break + fi + done + awk -v proxy_name="$proxy_name" -v method="$method" -v ss_password="$ss_password" -v local_ip="$local_ip" -v listen_port="$listen_port" -v stls_password="$stls_password" -v user_input="$user_input" '/^proxies:$/ {print; print " - name: " proxy_name; print " type: ss"; print " server:", local_ip; print " port:", listen_port; print " cipher:", method; print " password:", "\"" ss_password "\""; print " plugin: shadow-tls"; print " plugin-opts:"; print " host: \"" user_input "\""; print " password:", "\"" stls_password "\""; print " version: 3"; print ""; next} /- name: Proxy/ { print; flag_proxy=1; next } flag_proxy && flag_proxy++ == 3 { print " - " proxy_name } /- name: auto/ { print; flag_auto=1; next } flag_auto && flag_auto++ == 3 { print " - " proxy_name } 1' "$filename" > temp_file && mv temp_file "$filename" +} + +function generate_naive_win_client_config() { + local naive_client_file="$naive_client_filename" + sed -i -e "s,user_name,$user_name," -e "s,user_password,$user_password," -e "s,listen_port,$listen_port," -e "s,server_name,$domain," "$naive_client_file" + echo "电脑端配置文件已保存至$naive_client_file,请下载后使用!" +} + +function extract_types_tags() { + local config_file="/usr/local/etc/sing-box/config.json" + filtered_tags=() + types=() + tags=($(jq -r '.inbounds[] | select(.tag != null) | .tag' "$config_file")) + detour_tag=$(jq -r '.inbounds[] | select(.type == "shadowtls") | .detour' "$config_file") + wireguard_type=$(jq -r '.outbounds[] | select(.type == "wireguard" and .tag == "wireguard-out") | .type' "$config_file") + if [ -z "$tags" ] && [ -z "$wireguard_type" ]; then + echo "未检测到节点配置,请搭建节点后再使用本选项!" + exit 0 + fi + filtered_tags=() + for tag in "${tags[@]}"; do + if [ "$tag" != "$detour_tag" ]; then + filtered_tags+=("$tag") + fi + done + max_length=0 + for tag in "${filtered_tags[@]}"; do + tag_length=${#tag} + if ((tag_length > max_length)); then + max_length=$tag_length + fi + done + for ((i=0; i<${#filtered_tags[@]}; i++)); do + type=$(jq -r --arg tag "${filtered_tags[$i]}" '.inbounds[] | select(.tag == $tag) | .type' "$config_file") + types[$i]=$type + printf "%d).协议类型: %-20s 入站标签: %s\n" "$((i+1))" "$type" "${filtered_tags[$i]}" + done + if [ ! -z "$wireguard_type" ]; then + types[$i]=$wireguard_type + printf "%d).协议类型: %-20s 出站标签: %s\n" "$((i+1))" "$wireguard_type" "wireguard-out" + fi +} + +function delete_choice() { + local config_file="/usr/local/etc/sing-box/config.json" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local clash_yaml="/usr/local/etc/sing-box/clash.yaml" + local output_file="/usr/local/etc/sing-box/output.txt" + local temp_json="/usr/local/etc/sing-box/temp.json" + local temp_yaml="/usr/local/etc/sing-box/temp.yaml" + extract_types_tags + valid_choice=false + while [ "$valid_choice" == false ]; do + read -p "请选择要删除的节点配置(输入对应的数字): " choice + if [[ ! $choice =~ ^[0-9]+$ || $choice -lt 1 || $choice -gt ${#types[@]} ]]; then + echo -e "${RED}错误:无效的选择,请重新输入!${NC}" + else + valid_choice=true + fi + done + selected_tag="${filtered_tags[$choice-1]}" + selected_type="${types[$choice-1]}" + listen_port=$(jq -r --arg selected_tag "$selected_tag" '.inbounds[] | select(.tag == $selected_tag) | .listen_port' "$config_file" | awk '{print int($0)}') + if [ "$selected_type" == "wireguard" ]; then + jq '.outbounds |= map(select(.tag != "warp-IPv4-out" and .tag != "warp-IPv6-out" and .tag != "wireguard-out"))' "$config_file" > "$temp_json" + mv "$temp_json" "$config_file" + jq '.route.rules |= map(select(.outbound != "warp-IPv4-out" and .outbound != "warp-IPv6-out"))' "$config_file" > "$temp_json" + mv "$temp_json" "$config_file" + else + detour_tag=$(jq -r --arg selected_tag "$selected_tag" '.inbounds[] | select(.type == "shadowtls" and .tag == $selected_tag) | .detour' "$config_file") + jq --arg selected_tag "$selected_tag" --arg detour_tag "$detour_tag" '.inbounds |= map(select(.tag != $selected_tag and .tag != $detour_tag))' "$config_file" > "$temp_json" + mv "$temp_json" "$config_file" + jq --arg selected_tag "$selected_tag" '.route.rules |= map(select(.inbound[0] != $selected_tag))' "$config_file" > "$temp_json" + mv "$temp_json" "$config_file" + fi + if [ "$selected_type" != "wireguard" ]; then + awk -v port="$listen_port" '$0 ~ "监听端口: " port {print; in_block=1; next} in_block && NF == 0 {in_block=0} !in_block' "$output_file" > "$output_file.tmp1" + mv "$output_file.tmp1" "$output_file" + awk -v port="$listen_port" '$0 ~ "监听端口: " port {start=NR; next} {lines[NR]=$0} END {for (i=1; i<=NR; i++) if (i < start - 4 || i > start) print lines[i]}' "$output_file" > "$output_file.tmp2" + mv "$output_file.tmp2" "$output_file" + sed -i '/./,$!d' "$output_file" + fi + if [ -f "$clash_yaml" ]; then + get_clash_tags=$(awk '/proxies:/ {in_proxies_block=1} in_proxies_block && /- name:/ {name = $3} in_proxies_block && /port:/ {port = $2; print "Name:", name, "Port:", port}' "$clash_yaml" > "$temp_yaml") + matching_clash_tag=$(grep "Port: $listen_port" "$temp_yaml" | awk '{print $2}') + fi + if [ -n "$listen_port" ]; then + phone_matching_tag=$(jq -r --argjson listen_port "$listen_port" '.outbounds[] | select(.server_port == $listen_port) | .tag' "$phone_client_file") + win_matching_tag=$(jq -r --argjson listen_port "$listen_port" '.outbounds[] | select(.server_port == $listen_port) | .tag' "$win_client_file") + fi + jq --arg tag "$phone_matching_tag" '.outbounds |= map(select(.tag != $tag))' "$phone_client_file" > "$temp_json" + mv "$temp_json" "$phone_client_file" + jq --arg tag "$win_matching_tag" '.outbounds |= map(select(.tag != $tag))' "$win_client_file" > "$temp_json" + mv "$temp_json" "$win_client_file" + if [ -n "$matching_clash_tag" ] && [ "$selected_type" != "wireguard" ]; then + sed -i "/^ - name: $matching_clash_tag$/,/^\s*$/d" "$clash_yaml" + sed -i "/proxy-groups:/,/^\s*$/ {/ - $matching_clash_tag/d}" "$clash_yaml" + fi + phone_matching_detour=$(jq -r --arg phone_matching_tag "$phone_matching_tag" '.outbounds[] | select(.detour == $phone_matching_tag) | .detour' "$phone_client_file") + win_matching_detour=$(jq -r --arg win_matching_tag "$win_matching_tag" '.outbounds[] | select(.detour == $win_matching_tag) | .detour' "$win_client_file") + phone_matching_detour_tag=$(jq -r --arg phone_matching_detour "$phone_matching_detour" '.outbounds[] | select(.detour == $phone_matching_detour) | .tag' "$phone_client_file") + win_matching_detour_tag=$(jq -r --arg win_matching_detour "$win_matching_detour" '.outbounds[] | select(.detour == $win_matching_detour) | .tag' "$win_client_file") + awk -v phone_matching_tag="$phone_matching_tag" '!/^ "outbounds": \[$/,/^\s*]/{if (!($0 ~ "^ * \"" phone_matching_tag "\"")) print; else next; }' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" + awk -v win_matching_tag="$win_matching_tag" '!/^ "outbounds": \[$/,/^\s*]/{if (!($0 ~ "^ * \"" win_matching_tag "\"")) print; else next; }' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" + if [ "$phone_matching_tag" == "$phone_matching_detour" ]; then + jq --arg phone_matching_detour "$phone_matching_detour" '.outbounds |= map(select(.detour != $phone_matching_detour))' "$phone_client_file" > "$temp_json" + mv "$temp_json" "$phone_client_file" + awk -v phone_matching_detour_tag="$phone_matching_detour_tag" '!/^ "outbounds": \[$/,/^\s*]/{if (!($0 ~ "^ * \"" phone_matching_detour_tag "\"")) print; else next; }' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" + fi + if [ "$win_matching_tag" == "$win_matching_detour" ]; then + jq --arg win_matching_detour "$win_matching_detour" '.outbounds |= map(select(.detour != $win_matching_detour))' "$win_client_file" > "$temp_json" + mv "$temp_json" "$win_client_file" + awk -v win_matching_detour_tag="$win_matching_detour_tag" '!/^ "outbounds": \[$/,/^\s*]/{if (!($0 ~ "^ * \"" win_matching_detour_tag "\"")) print; else next; }' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" + fi + awk '{if ($0 ~ /],$/ && p ~ /,$/) sub(/,$/, "", p); if (NR > 1) print p; p = $0;}END{print p;}' "$phone_client_file" > "$phone_client_file.tmp" + mv "$phone_client_file.tmp" "$phone_client_file" + awk '{if ($0 ~ /],$/ && p ~ /,$/) sub(/,$/, "", p); if (NR > 1) print p; p = $0;}END{print p;}' "$win_client_file" > "$win_client_file.tmp" + mv "$win_client_file.tmp" "$win_client_file" + [ -f "$temp_yaml" ] && rm "$temp_yaml" + if ! jq -e 'select(.inbounds[] | .listen == "::")' "$config_file" > /dev/null; then + sed -i 's/"rules": \[\]/"rules": [\n ]/' "$config_file" + sed -i 's/^ "inbounds": \[\],/ "inbounds": [\n ],/' "$config_file" + sed -i 's/^ "outbounds": \[\],/ "outbounds": [\n ],/' "$win_client_file" + sed -i 's/^ "outbounds": \[\],/ "outbounds": [\n ],/' "$phone_client_file" + fi + systemctl restart sing-box + echo "已删除 $selected_type 的配置信息,服务端及客户端配置信息已更新,请下载新的配置文件使用!" +} + +function display_naive_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local num_users=${#user_names[@]} + echo -e "${CYAN}NaiveProxy 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $domain" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "用 户 名 密 码" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i=0; i> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function generate_naive_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local naive_client_file="$naive_client_filename" + local num_users=${#user_names[@]} + for ((i=0; i> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_juicity_config() { + local config_file="/usr/local/etc/juicity/config.json" + local output_file="/usr/local/etc/juicity/output.txt" + local server_address + local congestion_control=$(jq -r '.congestion_control' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [ -z "$domain" ]; then + server_address="$local_ip" + else + server_address="$domain" + fi + echo -e "${CYAN}Juicity 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "UUID:$user_uuids 密码:$user_passwords " | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "拥塞控制算法: $congestion_control" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "分享链接:" + juicity-server generate-sharelink -c "$config_file" + generate_juicity_win_client_config + echo "配置信息已保存至 $output_file" +} + +function display_http_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local num_users=${#user_names[@]} + local server_address + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [ -z "$domain" ]; then + server_address="$local_ip" + else + server_address="$domain" + fi + echo -e "${CYAN}HTTP 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "用 户 名 密 码" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i=0; i> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_http_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + for ((i=0; i<${#user_passwords[@]}; i++)); do + user_password="${user_passwords[$i]}" + if [ "$enable_ech" = true ]; then + write_phone_client_file + write_win_client_file + generate_http_win_client_config "$user_password" + generate_http_phone_client_config "$user_password" + else + write_phone_client_file + write_win_client_file + generate_http_win_client_config "$user_password" + generate_http_phone_client_config "$user_password" + ensure_clash_yaml + write_clash_yaml + generate_http_yaml + fi + done + if [ "$enable_ech" = true ]; then + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + else + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file ,请下载使用!" + fi +} + +function display_tuic_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local server_address + local congestion_control=$(jq -r '.inbounds[0].congestion_control' "$config_file") + local alpn=$(jq -r '.inbounds[0].tls.alpn[0]' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [ -z "$domain" ]; then + server_address="$local_ip" + else + server_address="$domain" + fi + echo -e "${CYAN}TUIC 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "用户密码列表:" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + echo " 用户名 UUID 密码" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_uuid="${user_uuids[$i]}" + user_password="${user_passwords[$i]}" + printf "%-13s %-42s %s\n" "$user_name" "$user_uuid" "$user_password" | tee -a "$output_file" + done + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "拥塞控制算法: $congestion_control" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "ALPN: $alpn" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_tuic_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local congestion_control=$(jq -r '.inbounds[0].congestion_control' "$config_file") + local alpn=$(jq -r '.inbounds[0].tls.alpn[0]' "$config_file") + local num_users=${#user_uuids[@]} + for ((i=0; i> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_Shadowsocks_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local ss_method=$(jq -r '.inbounds[0].method' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + write_phone_client_file + write_win_client_file + generate_shadowsocks_win_client_config + generate_shadowsocks_phone_client_config + ensure_clash_yaml + write_clash_yaml + generate_shadowsocks_yaml + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file ,请下载使用!" +} + +function display_socks_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + echo -e "${CYAN}SOCKS 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $local_ip" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "用户密码列表:" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + echo "用户名 密码" | tee -a "$output_file" + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_password="${user_passwords[$i]}" + printf "%-35s %s\n" "$user_name" "$user_password" | tee -a "$output_file" + done + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "节点配置信息已保存至 $output_file" +} + +function display_socks_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local num_users=${#user_names[@]} + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + for ((i=0; i> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_Hysteria_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local alpn=$(jq -r '.inbounds[0].tls.alpn[0]' "$config_file") + for ((i=0; i<${#user_passwords[@]}; i++)); do + user_password="${user_passwords[$i]}" + if [ "$enable_ech" = true ]; then + write_phone_client_file + write_win_client_file + generate_Hysteria_win_client_config "$user_password" + generate_Hysteria_phone_client_config "$user_password" + else + write_phone_client_file + write_win_client_file + generate_Hysteria_win_client_config "$user_password" + generate_Hysteria_phone_client_config "$user_password" + ensure_clash_yaml + write_clash_yaml + generate_Hysteria_yaml + fi + done + if [ "$enable_ech" = true ]; then + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + else + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file ,请下载使用!" + fi +} + +function display_Hy2_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local server_address + local alpn=$(jq -r '.inbounds[0].tls.alpn[0]' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [ -z "$domain" ]; then + server_address="$local_ip" + else + server_address="$domain" + fi + echo -e "${CYAN}Hysteria2 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址:$server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口:$listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "上行速度:${up_mbps}Mbps" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "下行速度:${down_mbps}Mbps" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "ALPN:$alpn" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "用户名 密码" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i=0; i<${#user_names[@]}; i++)); do + user_name="${user_names[$i]}" + user_password="${user_passwords[$i]}" + printf "%-35s %s\n" "$user_name" "$user_password" | tee -a "$output_file" + done + if [ -n "$obfs_password" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "QUIC 流量混淆器密码:$obfs_password" | tee -a "$output_file" + fi + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_Hy2_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local alpn=$(jq -r '.inbounds[0].tls.alpn[0]' "$config_file") + for ((i=0; i<${#user_passwords[@]}; i++)); do + user_password="${user_passwords[$i]}" + if [ "$enable_ech" = true ]; then + write_phone_client_file + write_win_client_file + generate_Hysteria2_win_client_config "$user_password" + generate_Hysteria2_phone_client_config "$user_password" + else + write_phone_client_file + write_win_client_file + generate_Hysteria2_win_client_config "$user_password" + generate_Hysteria2_phone_client_config "$user_password" + ensure_clash_yaml + write_clash_yaml + generate_Hysteria2_yaml + fi + done + if [ "$enable_ech" = true ]; then + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + else + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file ,请下载使用!" + fi +} + +function display_reality_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local flow_type=$(jq -r '.inbounds[0].users[0].flow' "$config_file") + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local server_name=$(jq -r '.inbounds[0].tls.server_name' "$config_file") + local target_server=$(jq -r '.inbounds[0].tls.reality.handshake.server' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + local lobal_public_key="$public_key" + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [[ "$flow_type" == "xtls-rprx-vision" ]]; then + transport_type="tcp" + fi + echo -e "${CYAN}VLESS 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $local_ip" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "UUID列表:" | tee -a "$output_file" + for ((i=0; i<${#user_uuids[@]}; i++)); do + user_uuid="${user_uuids[$i]}" + echo "$user_uuid"| tee -a "$output_file" + done + if [ -n "$flow_type" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "流控类型: $flow_type" | tee -a "$output_file" + fi + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + if [ "$transport_type" != "null" ]; then + echo "传输协议: $transport_type" | tee -a "$output_file" + if [ "$transport_type" == "ws" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "httpupgrade" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "grpc" ]; then + echo "grpc-service-name: $transport_service_name" | tee -a "$output_file" + fi + else + echo "传输协议: tcp" | tee -a "$output_file" + fi + if [ -n "$server_name" ] && [ "$server_name" != "null" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "ServerName: $server_name" | tee -a "$output_file" + fi + if [ -n "$target_server" ] && [ "$target_server" != "null" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "目标网站地址: $target_server" | tee -a "$output_file" + fi + if [ -n "$short_id" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "Short ID:" | tee -a "$output_file" + for ((i=0; i<${#short_ids[@]}; i++)); do + short_id="${short_ids[$i]}" + echo "$short_id" | tee -a "$output_file" + done + fi + if [ -n "$public_key" ]; then + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "PublicKey: $public_key" | tee -a "$output_file" + fi + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_reality_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local flow_type=$(jq -r '.inbounds[0].users[0].flow' "$config_file") + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local server_name=$(jq -r '.inbounds[0].tls.server_name' "$config_file") + local target_server=$(jq -r '.inbounds[0].tls.reality.handshake.server' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + local lobal_public_key="$public_key" + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + for ((i=0; i<${#user_uuids[@]}; i++)); do + local user_uuid="${user_uuids[$i]}" + write_phone_client_file + write_win_client_file + if [[ "$server_name" == "null" ]] && [[ "$transport_type" == "null" ]]; then + ensure_clash_yaml + write_clash_yaml + generate_vless_tcp_yaml + generate_vless_win_client_config + generate_vless_phone_client_config + elif [[ "$server_name" == "null" ]] && [[ "$transport_type" == "ws" ]]; then + ensure_clash_yaml + write_clash_yaml + generate_vless_ws_yaml + generate_vless_win_client_config + generate_vless_phone_client_config + elif [[ "$server_name" == "null" ]] && [[ "$transport_type" == "grpc" ]]; then + ensure_clash_yaml + write_clash_yaml + generate_vless_grpc_yaml + generate_vless_win_client_config + generate_vless_phone_client_config + elif [[ "$server_name" == "null" ]] && [[ "$transport_type" == "httpupgrade" ]]; then + generate_vless_win_client_config + generate_vless_phone_client_config + fi + for ((j=0; j<${#short_ids[@]}; j++)); do + local short_id="${short_ids[$j]}" + write_phone_client_file + write_win_client_file + if [[ -n "$server_name" ]] && [[ "$server_name" != "null" ]] && [[ "$transport_type" == "null" ]]; then + ensure_clash_yaml + write_clash_yaml + generate_vless_reality_vision_yaml + generate_vless_win_client_config + generate_vless_phone_client_config + elif [[ -n "$server_name" ]] && [[ "$server_name" != "null" ]] && [[ "$transport_type" == "http" ]]; then + generate_vless_win_client_config + generate_vless_phone_client_config + elif [[ -n "$server_name" ]] && [[ "$server_name" != "null" ]] && [[ "$transport_type" == "grpc" ]]; then + ensure_clash_yaml + write_clash_yaml + generate_vless_reality_grpc_yaml + generate_vless_win_client_config + generate_vless_phone_client_config + fi + done + done + if [[ "$transport_type" != "http" && "$transport_type" != "httpupgrade" ]]; then + echo "Clash配置文件已保存至 $clash_file,请下载使用!" + fi + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" +} + +function display_vmess_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local server_address + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local transport_path=$(jq -r '.inbounds[0].transport.path' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_address="$local_ip" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_address="$local_ip" + elif [[ -n "$domain" ]]; then + server_address="$domain" + fi + echo -e "${CYAN}VMess 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "UUID列表:" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i=0; i<${#user_uuids[@]}; i++)); do + user_uuid="${user_uuids[$i]}" + echo "$user_uuid"| tee -a "$output_file" + done + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + if [ "$transport_type" != "null" ]; then + echo "传输协议: $transport_type" | tee -a "$output_file" + if [ "$transport_type" == "ws" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "httpupgrade" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "grpc" ]; then + echo "grpc-service-name: $transport_service_name" | tee -a "$output_file" + fi + else + echo "传输协议: tcp" | tee -a "$output_file" + fi + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_vmess_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local transport_path=$(jq -r '.inbounds[0].transport.path' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + local show_clash_message=true + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + for ((i=0; i<${#user_uuids[@]}; i++)); do + user_uuid="${user_uuids[$i]}" + write_phone_client_file + write_win_client_file + generate_vmess_win_client_config + generate_vmess_phone_client_config + if [ "$enable_ech" != true ] && [ -z "$domain" ] && [ -z "$domain_name" ] && [ "$transport_type" == "null" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_tcp_yaml + elif [ "$enable_ech" != true ] && [ -z "$domain" ] && [ -z "$domain_name" ] && [ "$transport_type" == "ws" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_ws_yaml + elif [ "$enable_ech" != true ] && [ -z "$domain" ] && [ -z "$domain_name" ] && [ "$transport_type" == "grpc" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_grpc_yaml + elif [ "$enable_ech" != true ] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "null" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_tcp_tls_yaml + elif [ "$enable_ech" != true ] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "ws" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_ws_tls_yaml + elif [ "$enable_ech" != true ] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "grpc" ]; then + ensure_clash_yaml + write_clash_yaml + generate_vmess_grpc_tls_yaml + elif [ "$enable_ech" != true ] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "http" ]; then + show_clash_message=false + fi + done + if [ "$transport_type" == "http" ] || [ "$transport_type" == "httpupgrade" ] || [ "$enable_ech" = true ]; then + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + else + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file,请下载使用!" + fi +} + +function display_trojan_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local server_address + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local transport_path=$(jq -r '.inbounds[0].transport.path' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + if [[ -z "$domain" && -n "$domain_name" ]]; then + server_address="$local_ip" + elif [[ -z "$domain" && -z "$domain_name" ]]; then + server_address="$local_ip" + elif [[ -n "$domain" ]]; then + server_address="$domain" + fi + echo -e "${CYAN}Trojan 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $server_address" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "密码列表:" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i = 0; i < ${#user_passwords[@]}; i++)); do + user_password="${user_passwords[i]}" + echo "$user_password"| tee -a "$output_file" + done + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + if [ "$transport_type" != "null" ]; then + echo "传输协议: $transport_type" | tee -a "$output_file" + if [ "$transport_type" == "ws" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "httpupgrade" ]; then + echo "路径: $transport_path" | tee -a "$output_file" + elif [ "$transport_type" == "grpc" ]; then + echo "grpc-service-name: $transport_service_name" | tee -a "$output_file" + fi + else + echo "传输协议: tcp" | tee -a "$output_file" + fi + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_trojan_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local transport_type=$(jq -r '.inbounds[0].transport.type' "$config_file") + local transport_path=$(jq -r '.inbounds[0].transport.path' "$config_file") + local transport_service_name=$(jq -r '.inbounds[0].transport.service_name' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + for ((i = 0; i < ${#user_passwords[@]}; i++)); do + user_password="${user_passwords[i]}" + write_phone_client_file + write_win_client_file + generate_trojan_win_client_config + generate_trojan_phone_client_config + if [[ "$enable_ech" != true ]] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "null" ]; then + ensure_clash_yaml + write_clash_yaml + generate_trojan_tcp_tls_yaml + elif [[ "$enable_ech" != true ]] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "ws" ]; then + ensure_clash_yaml + write_clash_yaml + generate_trojan_ws_tls_yaml + elif [[ "$enable_ech" != true ]] && [[ -n "$domain" || -n "$domain_name" ]] && [ "$transport_type" == "grpc" ]; then + ensure_clash_yaml + write_clash_yaml + generate_trojan_grpc_tls_yaml + fi + done + if [[ "$enable_ech" != true ]] && [[ -n "$domain" || -n "$domain_name" ]] && [[ "$transport_type" != "http" || "$transport_type" != "httpupgrade" ]]; then + echo "Clash配置文件已保存至 $clash_file,请下载使用!" + fi + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" +} + +function display_shadowtls_config_info() { + local config_file="/usr/local/etc/sing-box/config.json" + local output_file="/usr/local/etc/sing-box/output.txt" + local user_input=$(jq -r '.inbounds[0].handshake.server' "$config_file") + local method=$(jq -r '.inbounds[1].method' "$config_file") + if [[ -n "$ip_v4" ]]; then + local_ip="$ip_v4" + elif [[ -n "$ip_v6" ]]; then + local_ip="$ip_v6" + fi + echo -e "${CYAN}ShadowTLS 节点配置信息:${NC}" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "服务器地址: $local_ip" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "监听端口: $listen_port" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "加密方式: $method" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "ShadowTLS用户名 ShadowTLS密码" | tee -a "$output_file" + echo "------------------------------------------------------------------------------" | tee -a "$output_file" + for ((i = 0; i < ${#stls_passwords[@]}; i++)); do + local stls_password="${stls_passwords[i]}" + printf "%-25s %s\n" "$user_name" "$stls_password" | tee -a "$output_file" + done + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "Shadowsocks 密码: $ss_passwords" | tee -a "$output_file" + echo -e "${CYAN}------------------------------------------------------------------------------${NC}" | tee -a "$output_file" + echo "握手服务器地址: $user_input" | tee -a "$output_file" + echo -e "${CYAN}==============================================================================${NC}" | tee -a "$output_file" + echo "" >> "$output_file" + echo "配置信息已保存至 $output_file" +} + +function display_shadowtls_config_files() { + local config_file="/usr/local/etc/sing-box/config.json" + local clash_file="/usr/local/etc/sing-box/clash.yaml" + local phone_client_file="/usr/local/etc/sing-box/phone_client.json" + local win_client_file="/usr/local/etc/sing-box/win_client.json" + local user_input=$(jq -r '.inbounds[0].handshake.server' "$config_file") + local method=$(jq -r '.inbounds[1].method' "$config_file") + for ((i = 0; i < ${#stls_passwords[@]}; i++)); do + local stls_password="${stls_passwords[i]}" + write_phone_client_file + write_win_client_file + generate_shadowtls_win_client_config "$stls_password" + generate_shadowtls_phone_client_config "$stls_password" + ensure_clash_yaml + write_clash_yaml + generate_shadowtls_yaml + done + echo "手机端配置文件已保存至$phone_client_file,请下载后使用!" + echo "电脑端配置文件已保存至$win_client_file,请下载后使用!" + echo "Clash配置文件已保存至 $clash_file ,请下载使用!" +} + +function view_saved_config() { + local config_paths=( + "/usr/local/etc/sing-box/output.txt" + "/usr/local/etc/juicity/output.txt" + ) + local found=false + for path in "${config_paths[@]}"; do + if [[ -f "$path" ]]; then + echo "配置信息文件 ($path):" + cat "$path" + found=true + fi + done + + if [[ "$found" == false ]]; then + echo "未找到保存的配置信息文件!" + fi +} + +function check_and_restart_services() { + if [ -f "/etc/systemd/system/sing-box.service" ]; then + systemctl restart sing-box.service + systemctl status --no-pager sing-box.service + fi + if [ -f "/etc/systemd/system/juicity.service" ]; then + systemctl restart juicity.service + systemctl status --no-pager juicity.service + fi +} + +function uninstall_sing_box() { + echo "开始卸载 sing-box..." + systemctl stop sing-box + systemctl disable sing-box + rm -rf /usr/local/bin/sing-box + rm -rf /usr/local/etc/sing-box + rm -rf /etc/systemd/system/sing-box.service + systemctl daemon-reload + echo "sing-box 卸载完成。" +} + +function uninstall_juicity() { + echo "开始卸载 juicity..." + systemctl stop juicity.service + systemctl disable juicity.service + rm -rf /etc/systemd/system/juicity.service + rm -rf /usr/local/etc/juicity + rm -rf /usr/local/bin/juicity-server + echo "juicity 卸载完成。" +} + +function update_proxy_tool() { + if [ -e /usr/local/bin/juicity-server ]; then + install_latest_juicity + fi + if [ -e /usr/local/bin/sing-box ]; then + select_sing_box_install_option + fi +} + +function uninstall() { + local uninstall_sing_box=false + local uninstall_juicity=false + if [[ -f "/etc/systemd/system/sing-box.service" ]] || [[ -f "/usr/local/bin/sing-box" ]] || [[ -d "/usr/local/etc/sing-box/" ]]; then + uninstall_sing_box=true + fi + if [[ -f "/etc/systemd/system/juicity.service" ]] || [[ -f "/usr/local/bin/juicity-server" ]] || [[ -d "/usr/local/etc/juicity/" ]]; then + uninstall_juicity=true + fi + if [[ "$uninstall_sing_box" == true ]]; then + uninstall_sing_box + fi + if [[ "$uninstall_juicity" == true ]]; then + uninstall_juicity + fi +} + +function check_wireguard_config() { + local config_file="/usr/local/etc/sing-box/config.json" + if grep -q "wireguard" "$config_file"; then + echo -e "${RED}Warp 已安装,请勿重复安装!${NC}" + exit 1 + fi +} + +function Update_Script() { + wget -O /root/singbox.sh https://raw.githubusercontent.com/qiuxiuya/qiuxiuya/main/VPS/singbox.sh + chmod +x /root/singbox.sh +} + +function add_cron_job() { + if command -v crontab > /dev/null && crontab -l | grep -q "singbox.sh"; then + echo "Cron job already exists." + else + (crontab -l 2>/dev/null ; echo "0 2 * * 1 /bin/bash /root/singbox.sh >> /usr/local/etc/certificate.log 2>&1") | crontab - + echo "Cron job added successfully." + fi +} + +function juicity_install() { + configure_dns64 + enable_bbr + create_juicity_folder + install_latest_juicity + get_local_ip + generate_juicity_config + add_cron_job + configure_juicity_service + systemctl daemon-reload + systemctl enable juicity.service + systemctl start juicity.service + systemctl restart juicity.service + display_juicity_config +} + +function Direct_install() { + install_sing_box + enable_bbr + log_outbound_config + set_listen_port + set_override_address + set_override_port + generate_Direct_config + modify_format_inbounds_and_outbounds + modify_route_rules + check_firewall_configuration + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_Direct_config +} + +function Shadowsocks_install() { + install_sing_box + enable_bbr + log_outbound_config + set_listen_port + select_encryption_method + set_ss_password + generate_ss_config + modify_format_inbounds_and_outbounds + modify_route_rules + check_firewall_configuration + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_Shadowsocks_config_info + display_Shadowsocks_config_files +} + +function socks_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_socks_config + modify_format_inbounds_and_outbounds + modify_route_rules + check_firewall_configuration + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_socks_config_info + display_socks_config_files +} + +function NaiveProxy_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_naive_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_naive_config_info + generate_naive_config_files +} + +function http_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_http_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_http_config_info + display_http_config_files +} + +function tuic_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_tuic_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_tuic_config_info + display_tuic_config_files +} + +function Hysteria_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_Hysteria_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_Hysteria_config_info + display_Hysteria_config_files +} + +function shadowtls_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_shadowtls_config + modify_format_inbounds_and_outbounds + modify_route_rules + check_firewall_configuration + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_shadowtls_config_info + display_shadowtls_config_files +} + +function reality_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_vless_config + modify_format_inbounds_and_outbounds + modify_route_rules + check_firewall_configuration + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + get_local_ip + display_reality_config_info + display_reality_config_files +} + +function Hysteria2_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_Hy2_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_Hy2_config_info + display_Hy2_config_files +} + +function trojan_install() { + install_sing_box + enable_bbr + log_outbound_config + generate_trojan_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_trojan_config_info + display_trojan_config_files +} + +function vmess_install() { + install_sing_box + enable_bbr + log_outbound_config + get_local_ip + generate_vmess_config + add_cron_job + modify_format_inbounds_and_outbounds + modify_route_rules + systemctl daemon-reload + systemctl enable sing-box + systemctl start sing-box + systemctl restart sing-box + display_vmess_config_info + display_vmess_config_files +} + +function wireguard_install() { + check_wireguard_config + check_config_file_existence + select_unlocked_items + geosite=() + update_geosite_array + select_outbound + update_route_file "$outbound" + get_temp_config_file + extract_variables_and_cleanup + update_outbound_file + systemctl restart sing-box +} + +function Update_certificate() { + get_local_ip + extract_tls_info + validate_tls_info + Reapply_certificates +} + +function main_menu() { +echo "╔════════════════════════════════════════════════════════════════════════╗" +echo -e "║ ${CYAN}作者${NC}: Mr. xiao ║" +echo -e "║ ${CYAN} Version:1.2 原作者已删库( ║" +echo "╠════════════════════════════════════════════════════════════════════════╣" +echo "║ 请选择要执行的操作: ║" +echo -e "║${CYAN} [1]${NC} SOCKS ${CYAN} [2]${NC} Direct ║" +echo -e "║${CYAN} [3]${NC} HTTP ${CYAN} [4]${NC} VMess ║" +echo -e "║${CYAN} [5]${NC} VLESS ${CYAN} [6]${NC} TUIC ║" +echo -e "║${CYAN} [7]${NC} Juicity ${CYAN} [8]${NC} Trojan ║" +echo -e "║${CYAN} [9]${NC} Hysteria ${CYAN} [10]${NC} Hysteria2 ║" +echo -e "║${CYAN} [11]${NC} ShadowTLS ${CYAN} [12]${NC} NaiveProxy ║" +echo -e "║${CYAN} [13]${NC} Shadowsocks ${CYAN} [14]${NC} WireGuard ║" +echo -e "║${CYAN} [15]${NC} 查看节点信息 ${CYAN} [16]${NC} 更新内核 ║" +echo -e "║${CYAN} [17]${NC} 更新脚本 ${CYAN} [18]${NC} 更新证书 ║" +echo -e "║${CYAN} [19]${NC} 重启服务 ${CYAN} [20]${NC} 节点管理 ║" +echo -e "║${CYAN} [21]${NC} 卸载 ${CYAN} [0]${NC} 退出 ║" +echo "╚════════════════════════════════════════════════════════════════════════╝" + + local choice + read -p "请选择 [0-21]: " choice + + case $choice in + 1) + socks_install + exit 0 + ;; + 2) + Direct_install + exit 0 + ;; + 3) + http_install + exit 0 + ;; + 4) + vmess_install + exit 0 + ;; + 5) + reality_install + exit 0 + ;; + 6) + tuic_install + exit 0 + ;; + 7) + juicity_install + exit 0 + ;; + 8) + trojan_install + exit 0 + ;; + 9) + Hysteria_install + exit 0 + ;; + 10) + Hysteria2_install + exit 0 + ;; + 11) + shadowtls_install + exit 0 + ;; + 12) + NaiveProxy_install + exit 0 + ;; + 13) + Shadowsocks_install + exit 0 + ;; + 14) + wireguard_install + exit 0 + ;; + 15) + view_saved_config + exit 0 + ;; + + 16) + update_proxy_tool + exit 0 + ;; + 17) + Update_Script + exit 0 + ;; + 18) + Update_certificate + ;; + 19) + check_and_restart_services + exit 0 + ;; + 20) + delete_choice + exit 0 + ;; + 21) + uninstall + exit 0 + ;; + 0) + echo "感谢使用 Mr. xiao 安装脚本!再见!" + exit 0 + ;; + *) + echo -e "${RED}无效的选择,请重新输入。${NC}" + main_menu + ;; + esac +} + +function run_option() { + case "$1" in + "18") + Update_certificate + exit 0 + ;; + esac +} + +if [ $# -eq 0 ]; then + main_menu +else + run_option "$1" +fi + +main_menu diff --git a/snell.sh b/snell.sh new file mode 100644 index 0000000..e69de29 diff --git a/ss-rust.sh b/ss-rust.sh new file mode 100644 index 0000000..20a006a --- /dev/null +++ b/ss-rust.sh @@ -0,0 +1,1867 @@ +#!/usr/bin/env bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#================================================= +# System Required: CentOS/Debian/Ubuntu +# Description: Shadowsocks Rust 管理脚本 +# Author: 翠花 +# WebSite: https://about.nange.cn +#================================================= + +# 当前脚本版本号 +sh_ver="1.5.3" + +# Shadowsocks Rust 相关路径 +SS_Folder="/etc/ss-rust" +SS_File="/usr/local/bin/ss-rust" +SS_Conf="/etc/ss-rust/config.json" +SS_Now_ver_File="/etc/ss-rust/ver.txt" + +# BBR 配置文件 +BBR_Local="/etc/sysctl.d/local.conf" + +# Shadow TLS 相关路径 +STLS_Folder="/etc/shadowtls" +STLS_File="/usr/local/bin/shadow-tls" +STLS_Conf="/etc/shadowtls/config.json" +STLS_Now_ver_File="/etc/shadowtls/ver.txt" + +Green_font_prefix="\033[32m" && Red_font_prefix="\033[31m" && Green_background_prefix="\033[42;37m" && Red_background_prefix="\033[41;37m" && Font_color_suffix="\033[0m" && Yellow_font_prefix="\033[0;33m" +Info="${Green_font_prefix}[信息]${Font_color_suffix}" +Error="${Red_font_prefix}[错误]${Font_color_suffix}" +Tip="${Yellow_font_prefix}[注意]${Font_color_suffix}" + +check_root(){ + if [[ $EUID != 0 ]]; then + echo -e "${Error} 当前非ROOT账号(或没有ROOT权限),无法继续操作,请更换ROOT账号或使用 ${Green_background_prefix}sudo su${Font_color_suffix} 命令获取临时ROOT权限(执行后可能会提示输入当前账号的密码)。" + exit 1 + fi +} + +check_sys(){ + if [[ -f /etc/redhat-release ]]; then + release="centos" + elif cat /etc/issue | grep -q -E -i "debian"; then + release="debian" + elif cat /etc/issue | grep -q -E -i "ubuntu"; then + release="ubuntu" + elif cat /etc/issue | grep -q -E -i "centos|red hat|redhat"; then + release="centos" + elif cat /proc/version | grep -q -E -i "debian"; then + release="debian" + elif cat /proc/version | grep -q -E -i "ubuntu"; then + release="ubuntu" + elif cat /proc/version | grep -q -E -i "centos|red hat|redhat"; then + release="centos" + fi +} + +sys_arch() { + uname=$(uname -m) + if [[ "$uname" == "i686" ]] || [[ "$uname" == "i386" ]]; then + arch="i686" + elif [[ "$uname" == *"armv7"* ]] || [[ "$uname" == "armv6l" ]]; then + arch="arm" + elif [[ "$uname" == *"armv8"* ]] || [[ "$uname" == "aarch64" ]]; then + arch="aarch64" + else + arch="x86_64" + fi +} + +#开启系统 TCP Fast Open +enable_systfo() { + kernel=$(uname -r | awk -F . '{print $1}') + if [ "$kernel" -ge 3 ]; then + echo 3 >/proc/sys/net/ipv4/tcp_fastopen + [[ ! -e $BBR_Local ]] && echo "fs.file-max = 51200 +net.core.rmem_max = 67108864 +net.core.wmem_max = 67108864 +net.core.rmem_default = 65536 +net.core.wmem_default = 65536 +net.core.netdev_max_backlog = 4096 +net.core.somaxconn = 4096 +net.ipv4.tcp_syncookies = 1 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_tw_recycle = 0 +net.ipv4.tcp_fin_timeout = 30 +net.ipv4.tcp_keepalive_time = 1200 +net.ipv4.ip_local_port_range = 10000 65000 +net.ipv4.tcp_max_syn_backlog = 4096 +net.ipv4.tcp_max_tw_buckets = 5000 +net.ipv4.tcp_fastopen = 3 +net.ipv4.tcp_rmem = 4096 87380 67108864 +net.ipv4.tcp_wmem = 4096 65536 67108864 +net.ipv4.tcp_mtu_probing = 1 +net.ipv4.tcp_ecn=1 +net.core.default_qdisc=fq +net.ipv4.tcp_congestion_control = bbr" >>/etc/sysctl.d/local.conf && sysctl --system >/dev/null 2>&1 + else + echo -e "$Error系统内核版本过低,无法支持 TCP Fast Open !" + fi +} + +check_installed_status(){ + [[ ! -e ${SS_File} ]] && echo -e "${Error} Shadowsocks Rust 没有安装,请检查!" && exit 1 +} + +check_status(){ + status=`systemctl status ss-rust | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1` +} + +check_new_ver(){ + new_ver=$(wget -qO- https://api.github.com/repos/shadowsocks/shadowsocks-rust/releases| jq -r '[.[] | select(.prerelease == false) | select(.draft == false) | .tag_name] | .[0]') + [[ -z ${new_ver} ]] && echo -e "${Error} Shadowsocks Rust 最新版本获取失败!" && exit 1 + echo -e "${Info} 检测到 Shadowsocks Rust 最新版本为 [ ${new_ver} ]" +} + +check_ver_comparison(){ + now_ver=$(cat ${SS_Now_ver_File}) + if [[ "${now_ver}" != "${new_ver}" ]]; then + echo -e "${Info} 发现 Shadowsocks Rust 已有新版本 [ ${new_ver} ],旧版本 [ ${now_ver} ]" + read -e -p "是否更新 ? [Y/n]:" yn + [[ -z "${yn}" ]] && yn="y" + if [[ $yn == [Yy] ]]; then + check_status + # [[ "$status" == "running" ]] && systemctl stop ss-rust + \cp "${SS_Conf}" "/tmp/config.json" + # rm -rf ${SS_Folder} + download + mv -f "/tmp/config.json" "${SS_Conf}" + restart + fi + else + echo -e "${Info} 当前 Shadowsocks Rust 已是最新版本 [ ${new_ver} ] !" && exit 1 + fi +} + +# 官方源 +stable_download() { + echo -e "${Info} 默认开始下载官方源 Shadowsocks Rust ……" + wget --no-check-certificate -N "https://github.com/shadowsocks/shadowsocks-rust/releases/download/${new_ver}/shadowsocks-${new_ver}.${arch}-unknown-linux-gnu.tar.xz" + if [[ ! -e "shadowsocks-${new_ver}.${arch}-unknown-linux-gnu.tar.xz" ]]; then + echo -e "${Error} Shadowsocks Rust 官方源下载失败!" + return 1 && exit 1 + else + tar -xvf "shadowsocks-${new_ver}.${arch}-unknown-linux-gnu.tar.xz" + fi + if [[ ! -e "ssserver" ]]; then + echo -e "${Error} Shadowsocks Rust 解压失败!" + echo -e "${Error} Shadowsocks Rust 安装失败 !" + return 1 && exit 1 + else + rm -rf "shadowsocks-${new_ver}.${arch}-unknown-linux-gnu.tar.xz" + chmod +x ssserver + mv -f ssserver "${SS_File}" + rm sslocal ssmanager ssservice ssurl + echo "${new_ver}" > ${SS_Now_ver_File} + + echo -e "${Info} Shadowsocks Rust 主程序下载安装完毕!" + return 0 + fi +} + +# 备用源 +backup_download() { + echo -e "${Info} 试图请求 备份源(旧版本) Shadowsocks Rust ……" + wget --no-check-certificate -N "https://raw.githubusercontent.com/xOS/Others/master/shadowsocks-rust/v1.14.1/shadowsocks-v1.14.1.${arch}-unknown-linux-gnu.tar.xz" + if [[ ! -e "shadowsocks-v1.14.1.${arch}-unknown-linux-gnu.tar.xz" ]]; then + echo -e "${Error} Shadowsocks Rust 备份源(旧版本) 下载失败!" + return 1 && exit 1 + else + tar -xvf "shadowsocks-v1.14.1.${arch}-unknown-linux-gnu.tar.xz" + fi + if [[ ! -e "ssserver" ]]; then + echo -e "${Error} Shadowsocks Rust 备份源(旧版本) 解压失败 !" + echo -e "${Error} Shadowsocks Rust 备份源(旧版本) 安装失败 !" + return 1 && exit 1 + else + rm -rf "shadowsocks-v1.14.1.${arch}-unknown-linux-gnu.tar.xz" + chmod +x ssserver + mv -f ssserver "${SS_File}" + rm sslocal ssmanager ssservice ssurl + echo "v1.14.1" > ${SS_Now_ver_File} + echo -e "${Info} Shadowsocks Rust 备份源(旧版本) 主程序下载安装完毕!" + return 0 + fi +} + +download() { + if [[ ! -e "${SS_Folder}" ]]; then + mkdir "${SS_Folder}" + # else + # [[ -e "${SS_File}" ]] && rm -rf "${SS_File}" + fi + stable_download + if [[ $? != 0 ]]; then + backup_download + fi +} + +# Shadow TLS 官方源下载 +stable_download_stls() { + echo -e "${Info} 默认开始下载官方源 Shadow TLS ……" + wget --no-check-certificate -N "https://github.com/ihciah/shadow-tls/releases/download/${stls_new_ver}/shadow-tls-${arch}-unknown-linux-musl" + if [[ ! -e "shadow-tls-${arch}-unknown-linux-musl" ]]; then + echo -e "${Error} Shadow TLS 官方源下载失败!" + return 1 && exit 1 + else + chmod +x "shadow-tls-${arch}-unknown-linux-musl" + mv -f "shadow-tls-${arch}-unknown-linux-musl" "${STLS_File}" + echo "${stls_new_ver}" > ${STLS_Now_ver_File} + echo -e "${Info} Shadow TLS 主程序下载安装完毕!" + return 0 + fi +} + +download_stls() { + if [[ ! -e "${STLS_Folder}" ]]; then + mkdir "${STLS_Folder}" + fi + stable_download_stls +} + +# Shadow TLS 配置相关函数 +set_stls_port(){ + while true + do + echo -e "${Tip} Shadow TLS 端口需与防火墙端口一致!" + echo -e "请输入 Shadow TLS 端口 [1-65535]" + read -e -p "(默认:8443):" stls_port + [[ -z "${stls_port}" ]] && stls_port="8443" + echo $((${stls_port}+0)) &>/dev/null + if [[ $? -eq 0 ]]; then + if [[ ${stls_port} -ge 1 ]] && [[ ${stls_port} -le 65535 ]]; then + echo && echo "========================================" + echo -e "Shadow TLS 端口:${Red_background_prefix} ${stls_port} ${Font_color_suffix}" + echo "========================================" && echo + break + else + echo "输入错误, 请输入正确的端口。" + fi + else + echo "输入错误, 请输入正确的端口。" + fi + done +} + +set_stls_password(){ + echo "请输入 Shadow TLS 密码 [0-9][a-z][A-Z]" + read -e -p "(默认:随机生成):" stls_password + [[ -z "${stls_password}" ]] && stls_password=$(< /dev/urandom tr -dc 'a-zA-Z0-9' | head -c 16) + echo && echo "========================================" + echo -e "Shadow TLS 密码:${Red_background_prefix} ${stls_password} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_stls_sni(){ + echo "请输入 Shadow TLS SNI 域名" + read -e -p "(默认:cloudflare.com):" stls_sni + [[ -z "${stls_sni}" ]] && stls_sni="cloudflare.com" + echo && echo "========================================" + echo -e "Shadow TLS SNI:${Red_background_prefix} ${stls_sni} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_stls_fastopen(){ + echo -e "是否开启 Shadow TLS TCP Fast Open ? +======================================== +${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭 +========================================" + read -e -p "(默认:1.开启):" stls_fastopen_choice + [[ -z "${stls_fastopen_choice}" ]] && stls_fastopen_choice="1" + if [[ ${stls_fastopen_choice} == "1" ]]; then + stls_fastopen="true" + else + stls_fastopen="false" + fi + echo && echo "========================================" + echo -e "Shadow TLS FastOpen:${Red_background_prefix} ${stls_fastopen} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_stls_strict(){ + echo -e "是否开启 Shadow TLS Strict 模式 ? +======================================== +${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭 +========================================" + read -e -p "(默认:1.开启):" stls_strict_choice + [[ -z "${stls_strict_choice}" ]] && stls_strict_choice="1" + if [[ ${stls_strict_choice} == "1" ]]; then + stls_strict="true" + else + stls_strict="false" + fi + echo && echo "========================================" + echo -e "Shadow TLS Strict:${Red_background_prefix} ${stls_strict} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_stls_tls_wildcard_sni(){ + echo -e "请选择 Shadow TLS Wildcard SNI 模式: +======================================== +${Green_font_prefix} 1.${Font_color_suffix} authed (默认) +${Green_font_prefix} 2.${Font_color_suffix} off +${Green_font_prefix} 3.${Font_color_suffix} all +========================================" + read -e -p "(默认:1.authed):" stls_tls_wildcard_sni_choice + [[ -z "${stls_tls_wildcard_sni_choice}" ]] && stls_tls_wildcard_sni_choice="1" + case "${stls_tls_wildcard_sni_choice}" in + 1) stls_tls_wildcard_sni="authed" ;; + 2) stls_tls_wildcard_sni="off" ;; + 3) stls_tls_wildcard_sni="all" ;; + *) stls_tls_wildcard_sni="authed" ;; + esac + echo && echo "========================================" + echo -e "TLS Wildcard SNI:${Red_background_prefix} ${stls_tls_wildcard_sni} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_stls_fallback(){ + echo "请输入 Shadow TLS 回退域名" + read -e -p "(默认:cloud.tencent.com:443):" stls_fallback + [[ -z "${stls_fallback}" ]] && stls_fallback="cloud.tencent.com:443" + echo && echo "========================================" + echo -e "Shadow TLS 回退域名:${Red_background_prefix} ${stls_fallback} ${Font_color_suffix}" + echo "========================================" && echo +} + +service(){ + echo " +[Unit] +Description= Shadowsocks Rust Service +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service +[Service] +LimitNOFILE=32767 +Type=simple +User=root +Restart=on-failure +RestartSec=5s +DynamicUser=true +ExecStartPre=/bin/sh -c 'ulimit -n 51200' +ExecStart=${SS_File} -c ${SS_Conf} +[Install] +WantedBy=multi-user.target" > /etc/systemd/system/ss-rust.service +systemctl enable --now ss-rust + echo -e "${Info} Shadowsocks Rust 服务配置完成!" +} + +service_stls(){ + cat > /etc/systemd/system/shadowtls.service<<-EOF +[Unit] +Description=Shadow TLS Service +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service + +[Service] +LimitNOFILE=32767 +Type=simple +User=root +Restart=on-failure +RestartSec=5s +Environment=MONOIO_FORCE_LEGACY_DRIVER=1 +ExecStartPre=/bin/sh -c 'ulimit -n 51200' +ExecStart=${STLS_File} config --config ${STLS_Conf} + +[Install] +WantedBy=multi-user.target +EOF + systemctl enable --now shadowtls + echo -e "${Info} Shadow TLS 服务配置完成!" +} + +installation_dependency(){ + if [[ ${release} == "centos" ]]; then + yum update + yum install jq gzip wget curl unzip xz openssl -y + else + apt-get update + apt-get install jq gzip wget curl unzip xz-utils openssl -y + fi + \cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +} + +write_config(){ + cat > ${SS_Conf}<<-EOF +{ + "server": "::", + "server_port": ${port}, + "password": "${password}", + "method": "${cipher}", + "fast_open": ${tfo}, + "mode": "tcp_and_udp", + "user":"nobody", + "timeout":300, + "nameserver":"1.1.1.1" +} +EOF +} + +write_stls_config(){ + # 设置默认值 + [[ -z "${stls_fastopen}" ]] && stls_fastopen="true" + [[ -z "${stls_strict}" ]] && stls_strict="true" + [[ -z "${stls_tls_wildcard_sni}" ]] && stls_tls_wildcard_sni="authed" + [[ -z "${stls_fallback}" ]] && stls_fallback="cloud.tencent.com:443" + + # 构建 dispatch 配置 + if [[ -z "${stls_dispatch}" ]]; then + # 没有现有配置,使用默认配置 + # SNI 域名对应的目标地址也使用相同的域名:443 + stls_dispatch_config="\"${stls_sni}\": \"${stls_sni}:443\", + \"captive.apple.com\": \"captive.apple.com:443\"" + else + # 有现有配置,需要智能更新 SNI + if [[ ! -z "${stls_sni}" && -e ${STLS_Conf} ]]; then + # 查找需要更新的 SNI(排除 captive.apple.com) + old_sni=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | keys[] | select(. != "captive.apple.com")' 2>/dev/null | head -1) + if [[ ! -z "${old_sni}" && "${old_sni}" != "null" && "${old_sni}" != "${stls_sni}" ]]; then + # 需要替换旧的 SNI 为新的 SNI,同时更新键和值 + echo -e "${Info} 更新主要 SNI 从 ${old_sni} 到 ${stls_sni}" + + # 使用 jq 来精确替换键和值 + stls_dispatch_config=$(cat ${STLS_Conf} | jq -r --arg old_sni "${old_sni}" --arg new_sni "${stls_sni}" ' + .server.tls_addr.dispatch | + to_entries | + map(if .key == $old_sni then (.key = $new_sni | .value = ($new_sni + ":443")) else . end) | + map("\"\(.key)\": \"\(.value)\"") | + join(",\n ") + ' 2>/dev/null) + + # 如果 jq 处理失败,回退到字符串替换 + if [[ -z "${stls_dispatch_config}" || "${stls_dispatch_config}" == "null" ]]; then + echo -e "${Info} jq 处理失败,使用字符串替换" + # 先替换键,再替换对应的值 + stls_dispatch_config=$(echo "${stls_dispatch}" | sed "s/\"${old_sni}\": \"[^\"]*\"/\"${stls_sni}\": \"${stls_sni}:443\"/g") + fi + else + # 如果没有找到需要更新的 SNI,或者 SNI 已经是正确的,直接使用现有配置 + stls_dispatch_config="${stls_dispatch}" + fi + else + stls_dispatch_config="${stls_dispatch}" + fi + fi + + # 调试信息(可选,用于排查问题) + # echo -e "${Info} 调试信息:" + # echo -e " stls_sni: ${stls_sni}" + # echo -e " stls_dispatch_config: ${stls_dispatch_config}" + + cat > ${STLS_Conf}<<-EOF +{ + "disable_nodelay": false, + "fastopen": ${stls_fastopen}, + "v3": true, + "strict": ${stls_strict}, + "server": { + "listen": "0.0.0.0:${stls_port}", + "server_addr": "127.0.0.1:${port}", + "tls_addr": { + "wildcard_sni": "${stls_tls_wildcard_sni}", + "dispatch": { + ${stls_dispatch_config} + }, + "fallback": "${stls_fallback}" + }, + "password": "${stls_password}", + "wildcard_sni": "authed" + } +} +EOF + + # 验证配置文件是否正确生成 + if [[ -e ${STLS_Conf} ]]; then + # 检查配置文件是否为有效的 JSON + if ! jq . ${STLS_Conf} >/dev/null 2>&1; then + echo -e "${Error} Shadow TLS 配置文件格式错误!" + return 1 + fi + echo -e "${Info} Shadow TLS 配置文件写入成功" + + # 显示当前配置的 SNI + current_sni=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | keys[0]' 2>/dev/null) + if [[ ! -z "${current_sni}" && "${current_sni}" != "null" ]]; then + echo -e "${Info} 当前配置的 SNI: ${Green_font_prefix}${current_sni}${Font_color_suffix}" + fi + else + echo -e "${Error} Shadow TLS 配置文件写入失败!" + return 1 + fi +} + +read_config(){ + [[ ! -e ${SS_Conf} ]] && echo -e "${Error} Shadowsocks Rust 配置文件不存在!" && exit 1 + port=$(cat ${SS_Conf}|jq -r '.server_port') + password=$(cat ${SS_Conf}|jq -r '.password') + cipher=$(cat ${SS_Conf}|jq -r '.method') + tfo=$(cat ${SS_Conf}|jq -r '.fast_open') +} + +read_stls_config(){ + [[ ! -e ${STLS_Conf} ]] && echo -e "${Error} Shadow TLS 配置文件不存在!" && exit 1 + stls_port=$(cat ${STLS_Conf}|jq -r '.server.listen' | grep -oE '[0-9]+$') + stls_password=$(cat ${STLS_Conf}|jq -r '.server.password') + stls_sni=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | keys[0]') + stls_fastopen=$(cat ${STLS_Conf}|jq -r '.fastopen') + stls_strict=$(cat ${STLS_Conf}|jq -r '.strict') + stls_tls_wildcard_sni=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.wildcard_sni') + stls_fallback=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.fallback') + # 读取完整的 dispatch 配置 + stls_dispatch=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map("\"\(.key)\": \"\(.value)\"") | join(",\n ")') +} + +set_port(){ + while true + do + echo -e "${Tip} 本步骤不涉及系统防火墙端口操作,请手动放行相应端口!" + echo -e "请输入 Shadowsocks Rust 端口 [1-65535]" + read -e -p "(默认:2525):" port + [[ -z "${port}" ]] && port="2525" + echo $((${port}+0)) &>/dev/null + if [[ $? -eq 0 ]]; then + if [[ ${port} -ge 1 ]] && [[ ${port} -le 65535 ]]; then + echo && echo "========================================" + echo -e "端口:${Red_background_prefix} ${port} ${Font_color_suffix}" + echo "========================================" && echo + break + else + echo "输入错误, 请输入正确的端口。" + fi + else + echo "输入错误, 请输入正确的端口。" + fi + done +} + +set_tfo(){ + echo -e "是否开启 TCP Fast Open ? +======================================== +${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭 +========================================" + read -e -p "(默认:1.开启):" tfo + [[ -z "${tfo}" ]] && tfo="1" + if [[ ${tfo} == "1" ]]; then + tfo=true + enable_systfo + else + tfo=false + fi + echo && echo "==================================" + echo -e "TCP Fast Open 开启状态:${Red_background_prefix} ${tfo} ${Font_color_suffix}" + echo "==================================" && echo +} + +set_password(){ + echo "请输入 Shadowsocks Rust 密码 [0-9][a-z][A-Z]" + read -e -p "(默认:随机生成):" password + # 当用户未输入密码时,执行默认生成逻辑 + if [[ -z "${password}" ]]; then + # 判断是否为2022系列加密 + if [[ ${cipher} == "2022-blake3-aes-256-gcm" || ${cipher} == "2022-blake3-chacha20-poly1305" ]]; then + # 2022系列必须使用指定长度的Base64密钥 + echo -e "${Tip} 为 ${cipher} 生成 32 字节 Base64 密钥..." + password=$(openssl rand -base64 32) + elif [[ ${cipher} == "2022-blake3-aes-128-gcm" ]]; then + # 2022系列必须使用指定长度的Base64密钥 + echo -e "${Tip} 为 ${cipher} 生成 16 字节 Base64 密钥..." + password=$(openssl rand -base64 16) + else + # 其他加密方式,生成一个普通的16位字母和数字的随机密码 + echo -e "${Tip} 为 ${cipher} 生成 16 位随机密码 (非Base64)..." + password=$(< /dev/urandom tr -dc 'a-zA-Z0-9' | head -c 16) + fi + fi + echo && echo "========================================" + echo -e "密码:${Red_font_prefix} ${password} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_cipher(){ + echo -e "请选择 Shadowsocks Rust 加密方式 +======================================== + ${Green_font_prefix} 1.${Font_color_suffix} aes-128-gcm ${Green_font_prefix}(默认)${Font_color_suffix} + ${Green_font_prefix} 2.${Font_color_suffix} aes-256-gcm ${Green_font_prefix}(推荐)${Font_color_suffix} + ${Green_font_prefix} 3.${Font_color_suffix} chacha20-ietf-poly1305 ${Green_font_prefix}${Font_color_suffix} + ${Green_font_prefix} 4.${Font_color_suffix} plain ${Red_font_prefix}(不推荐)${Font_color_suffix} + ${Green_font_prefix} 5.${Font_color_suffix} none ${Red_font_prefix}(不推荐)${Font_color_suffix} + ${Green_font_prefix} 6.${Font_color_suffix} table + ${Green_font_prefix} 7.${Font_color_suffix} aes-128-cfb + ${Green_font_prefix} 8.${Font_color_suffix} aes-256-cfb + ${Green_font_prefix} 9.${Font_color_suffix} aes-256-ctr + ${Green_font_prefix}10.${Font_color_suffix} camellia-256-cfb + ${Green_font_prefix}11.${Font_color_suffix} rc4-md5 + ${Green_font_prefix}12.${Font_color_suffix} chacha20-ietf +======================================== + ${Tip} AEAD 2022 加密(须v1.15.0及以上版本且密码须经过Base64加密) +======================================== + ${Green_font_prefix}13.${Font_color_suffix} 2022-blake3-aes-128-gcm ${Green_font_prefix}(推荐)${Font_color_suffix} + ${Green_font_prefix}14.${Font_color_suffix} 2022-blake3-aes-256-gcm ${Green_font_prefix}(推荐)${Font_color_suffix} + ${Green_font_prefix}15.${Font_color_suffix} 2022-blake3-chacha20-poly1305 + ======================================== + ${Tip} 如需其它加密方式请手动修改配置文件 !" && echo + read -e -p "(默认: 1. aes-128-gcm):" cipher + [[ -z "${cipher}" ]] && cipher="1" + if [[ ${cipher} == "1" ]]; then + cipher="aes-128-gcm" + elif [[ ${cipher} == "2" ]]; then + cipher="aes-256-gcm" + elif [[ ${cipher} == "3" ]]; then + cipher="chacha20-ietf-poly1305" + elif [[ ${cipher} == "4" ]]; then + cipher="plain" + elif [[ ${cipher} == "5" ]]; then + cipher="none" + elif [[ ${cipher} == "6" ]]; then + cipher="table" + elif [[ ${cipher} == "7" ]]; then + cipher="aes-128-cfb" + elif [[ ${cipher} == "8" ]]; then + cipher="aes-256-cfb" + elif [[ ${cipher} == "9" ]]; then + cipher="aes-256-ctr" + elif [[ ${cipher} == "10" ]]; then + cipher="camellia-256-cfb" + elif [[ ${cipher} == "11" ]]; then + cipher="arc4-md5" + elif [[ ${cipher} == "12" ]]; then + cipher="chacha20-ietf" + elif [[ ${cipher} == "13" ]]; then + cipher="2022-blake3-aes-128-gcm" + elif [[ ${cipher} == "14" ]]; then + cipher="2022-blake3-aes-256-gcm" + elif [[ ${cipher} == "15" ]]; then + cipher="2022-blake3-chacha20-poly1305" + else + cipher="aes-128-gcm" + fi + echo && echo "========================================" + echo -e "加密:${Red_background_prefix} ${cipher} ${Font_color_suffix}" + echo "========================================" && echo +} + +set_config(){ + check_installed_status + echo && echo -e "你要做什么? +======================================== + ${Green_font_prefix}1.${Font_color_suffix} 修改 端口配置 + ${Green_font_prefix}2.${Font_color_suffix} 修改 加密配置 + ${Green_font_prefix}3.${Font_color_suffix} 修改 密码配置 + ${Green_font_prefix}4.${Font_color_suffix} 修改 TFO 配置 +======================================== + ${Green_font_prefix}5.${Font_color_suffix} 修改 全部配置" && echo + read -e -p "(默认:取消):" modify + [[ -z "${modify}" ]] && echo "已取消..." && exit 1 + if [[ "${modify}" == "1" ]]; then + read_config + set_port + cipher=${cipher} + password=${password} + tfo=${tfo} + write_config + restart + elif [[ "${modify}" == "2" ]]; then + read_config + set_cipher + port=${port} + password=${password} + tfo=${tfo} + write_config + restart + elif [[ "${modify}" == "3" ]]; then + read_config + cipher=${cipher} + set_password + port=${port} + tfo=${tfo} + write_config + restart + elif [[ "${modify}" == "4" ]]; then + read_config + set_tfo + cipher=${cipher} + port=${port} + password=${password} + write_config + restart + elif [[ "${modify}" == "5" ]]; then + read_config + set_port + set_cipher + set_password + set_tfo + write_config + restart + else + echo -e "${Error} 请输入正确的数字(1-5)" && exit 1 + fi +} + +install(){ + [[ -e ${SS_File} ]] && echo -e "${Error} 检测到 Shadowsocks Rust 已安装!" && exit 1 + echo -e "${Info} 开始设置 配置..." + set_port + set_cipher + set_password + set_tfo + echo -e "${Info} 开始安装/配置 依赖..." + installation_dependency + echo -e "${Info} 开始下载/安装..." + check_new_ver + download + echo -e "${Info} 开始安装系统服务脚本..." + service + echo -e "${Info} 开始写入 配置文件..." + write_config + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + start + echo -e "${Info} Shadowsocks Rust 安装完成!" + + # 询问是否继续安装 Shadow TLS + echo && echo -e "${Tip} 是否继续安装 Shadow TLS 流量伪装?" + read -e -p "(默认: N 不安装) [y/N]: " install_stls_choice + [[ -z "${install_stls_choice}" ]] && install_stls_choice="n" + + if [[ ${install_stls_choice} == [Yy] ]]; then + echo -e "${Info} 开始安装 Shadow TLS..." + install_stls_after_ss + else + echo -e "${Info} 跳过 Shadow TLS 安装,显示 Shadowsocks Rust 配置..." + view_ss_only + fi +} + +install_stls(){ + [[ -e ${STLS_File} ]] && echo -e "${Error} 检测到 Shadow TLS 已安装!" && exit 1 + + # 检查 Shadowsocks Rust 是否已安装 + if [[ ! -e ${SS_File} ]]; then + echo -e "${Error} 检测到 Shadowsocks Rust 尚未安装!" + echo -e "${Info} Shadow TLS 需要配合 Shadowsocks Rust 使用。" + echo -e "${Info} 建议先安装 Shadowsocks Rust,再安装 Shadow TLS。" + echo && read -e -p "是否现在安装 Shadowsocks Rust?[Y/n]:" install_ss_choice + [[ -z "${install_ss_choice}" ]] && install_ss_choice="Y" + if [[ ${install_ss_choice} == [Yy] ]]; then + echo -e "${Info} 开始安装 Shadowsocks Rust..." + install + echo -e "${Info} Shadowsocks Rust 安装完成,现在开始安装 Shadow TLS..." + else + echo -e "${Info} 已取消安装,返回上级菜单..." + shadowtls_menu + return + fi + fi + + echo -e "${Info} 开始设置 Shadow TLS 配置..." + read_config # 读取现有 SS 配置 + + # 使用交互式配置,每项都有默认值 + echo -e "${Info} 请配置 Shadow TLS 参数(可直接回车使用默认值):" + + set_stls_port + set_stls_password + set_stls_sni + set_stls_fastopen + set_stls_strict + set_stls_tls_wildcard_sni + set_stls_fallback + manage_stls_dispatch + + echo -e "${Info} 开始下载/安装 Shadow TLS..." + check_stls_new_ver + download_stls + echo -e "${Info} 开始安装 Shadow TLS 服务脚本..." + service_stls + echo -e "${Info} 开始写入 Shadow TLS 配置文件..." + write_stls_config + echo -e "${Info} Shadow TLS 安装完毕,开始启动..." + start_stls + echo -e "${Info} Shadow TLS 安装完成!显示完整配置信息..." + view_combined_config_with_return +} + +install_stls_after_ss(){ + echo -e "${Info} 开始设置 Shadow TLS 配置..." + read_config # 读取现有 SS 配置 + + # 使用交互式配置,每项都有默认值 + echo -e "${Info} 请配置 Shadow TLS 参数(可直接回车使用默认值):" + + set_stls_port + set_stls_password + set_stls_sni + set_stls_fastopen + set_stls_strict + set_stls_tls_wildcard_sni + set_stls_fallback + manage_stls_dispatch + + echo -e "${Info} 开始下载/安装 Shadow TLS..." + check_stls_new_ver + download_stls + echo -e "${Info} 开始安装 Shadow TLS 服务脚本..." + service_stls + echo -e "${Info} 开始写入 Shadow TLS 配置文件..." + write_stls_config + echo -e "${Info} Shadow TLS 安装完毕,开始启动..." + start_stls + echo -e "${Info} Shadow TLS 安装完成!显示完整配置信息..." + view_combined_config_with_return +} + +view(){ + check_installed_status + read_config + getipv4 + getipv6 + link_qr + clear && echo + echo -e "Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " 端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e " TFO :${Green_font_prefix}${tfo}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + [[ ! -z "${link_ipv4}" ]] && echo -e "${link_ipv4}" + [[ ! -z "${link_ipv6}" ]] && echo -e "${link_ipv6}" + echo -e "—————————————————————————" + echo -e "${Info} Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4},${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + else + echo -e "$(uname -n) = ss, ${ipv6},${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + fi + echo -e "—————————————————————————" + echo && echo -n " 按回车键返回主菜单..." && read + start_menu +} + +view_ss_only(){ + check_installed_status + read_config + getipv4 + getipv6 + link_qr + clear && echo + echo -e "Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " 端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e " TFO :${Green_font_prefix}${tfo}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + [[ ! -z "${link_ipv4}" ]] && echo -e "${link_ipv4}" + [[ ! -z "${link_ipv6}" ]] && echo -e "${link_ipv6}" + echo -e "—————————————————————————" + echo -e "${Info} Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4},${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + else + echo -e "$(uname -n) = ss, ${ipv6},${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + fi + echo -e "—————————————————————————" + echo && echo -n " 按回车键返回主菜单..." && read + start_menu +} + +view_combined_config(){ + local menu_source="$1" # 接收调用来源参数 + check_installed_status + read_config + getipv4 + getipv6 + link_qr + clear && echo + + echo -e "完整配置信息:" + echo -e "========================================" + + # 显示 Shadow TLS + SS 配置 + if [[ -e ${STLS_File} ]]; then + read_stls_config + echo -e "${Info} Shadow TLS + Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 服务器:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 服务器:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " Shadow TLS 端口:${Green_font_prefix}${stls_port}${Font_color_suffix}" + echo -e " Shadow TLS 密码:${Green_font_prefix}${stls_password}${Font_color_suffix}" + echo -e " Shadow TLS SNI:${Green_font_prefix}${stls_sni}${Font_color_suffix}" + echo -e " SS 本地端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " SS 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " SS 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + echo -e "${Info} Shadow TLS + SS Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4}, ${stls_port}, encrypt-method=${cipher}, password=${password}, shadow-tls-password=${stls_password}, shadow-tls-sni=${stls_sni}, shadow-tls-version=3, tfo=${tfo}, udp-relay=true, ecn=true, udp-port=${port}" + else + echo -e "$(uname -n) = ss, ${ipv6}, ${stls_port}, encrypt-method=${cipher}, password=${password}, shadow-tls-password=${stls_password}, shadow-tls-sni=${stls_sni}, shadow-tls-version=3, tfo=${tfo}, udp-relay=true, ecn=true, udp-port=${port}" + fi + echo && echo -e "========================================" + fi + + # 显示纯 SS 配置 + echo -e "${Info} 原始 Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " 端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + [[ ! -z "${link_ipv4}" ]] && echo -e "${link_ipv4}" + [[ ! -z "${link_ipv6}" ]] && echo -e "${link_ipv6}" + echo -e "—————————————————————————" + echo -e "${Info} 原始 SS Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4}, ${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + fi + echo -e "========================================" + + # 根据调用来源返回到相应菜单 + if [[ "$menu_source" == "shadowtls" ]]; then + echo && echo -n " 按回车键返回 Shadow TLS 菜单..." && read + shadowtls_menu + else + echo && echo -n " 按回车键返回主菜单..." && read + start_menu + fi +} + +view_combined_config_with_return(){ + check_installed_status + read_config + getipv4 + getipv6 + link_qr + clear && echo + + echo -e "完整配置信息:" + echo -e "========================================" + + # 显示 Shadow TLS + SS 配置 + if [[ -e ${STLS_File} ]]; then + read_stls_config + echo -e "${Info} Shadow TLS + Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 服务器:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 服务器:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " Shadow TLS 端口:${Green_font_prefix}${stls_port}${Font_color_suffix}" + echo -e " Shadow TLS 密码:${Green_font_prefix}${stls_password}${Font_color_suffix}" + echo -e " Shadow TLS SNI:${Green_font_prefix}${stls_sni}${Font_color_suffix}" + echo -e " SS 本地端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " SS 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " SS 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + echo -e "${Info} Shadow TLS + SS Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4}, ${stls_port}, encrypt-method=${cipher}, password=${password}, shadow-tls-password=${stls_password}, shadow-tls-sni=${stls_sni}, shadow-tls-version=3, tfo=${tfo}, ecn=true, udp-relay=true, udp-port=${port}" + else + echo -e "$(uname -n) = ss, ${ipv6}, ${stls_port}, encrypt-method=${cipher}, password=${password}, shadow-tls-password=${stls_password}, shadow-tls-sni=${stls_sni}, shadow-tls-version=3, tfo=${tfo}, ecn=true, udp-relay=true, udp-port=${port}" + fi + echo && echo -e "========================================" + fi + + # 显示纯 SS 配置 + echo -e "${Info} 原始 Shadowsocks Rust 配置:" + echo -e "————————————————————————————————————————" + [[ "${ipv4}" != "IPv4_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv4}${Font_color_suffix}" + [[ "${ipv6}" != "IPv6_Error" ]] && echo -e " 地址:${Green_font_prefix}${ipv6}${Font_color_suffix}" + echo -e " 端口:${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " 密码:${Green_font_prefix}${password}${Font_color_suffix}" + echo -e " 加密:${Green_font_prefix}${cipher}${Font_color_suffix}" + echo -e "————————————————————————————————————————" + [[ ! -z "${link_ipv4}" ]] && echo -e "${link_ipv4}" + [[ ! -z "${link_ipv6}" ]] && echo -e "${link_ipv6}" + echo -e "—————————————————————————" + echo -e "${Info} SS Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e "$(uname -n) = ss, ${ipv4}, ${port}, encrypt-method=${cipher}, password=${password}, tfo=${tfo}, udp-relay=true, ecn=true" + fi + echo -e "========================================" + echo && echo -n " 按回车键继续..." && read +} + +# 综合 Shadow TLS 配置函数 +set_stls_config(){ + check_stls_installed_status + read_config + read_stls_config + + echo && echo -e "你要修改哪项 Shadow TLS 配置? +======================================== + ${Green_font_prefix}1.${Font_color_suffix} 修改 端口配置 + ${Green_font_prefix}2.${Font_color_suffix} 修改 密码配置 + ${Green_font_prefix}3.${Font_color_suffix} 修改 Shadow TLS SNI 域名 + ${Green_font_prefix}4.${Font_color_suffix} 修改 FastOpen 配置 + ${Green_font_prefix}5.${Font_color_suffix} 修改 Strict 模式配置 + ${Green_font_prefix}6.${Font_color_suffix} 修改 TLS Wildcard SNI 配置 + ${Green_font_prefix}7.${Font_color_suffix} 修改 回退域名配置 + ${Green_font_prefix}8.${Font_color_suffix} 管理 Dispatch 分发配置 +======================================== + ${Green_font_prefix}9.${Font_color_suffix} 修改 全部配置" && echo + read -e -p "(默认:取消):" modify + [[ -z "${modify}" ]] && echo "已取消..." && return + + case "${modify}" in + 1) + set_stls_port + write_stls_config + restart_stls + ;; + 2) + set_stls_password + write_stls_config + restart_stls + ;; + 3) + set_stls_sni + write_stls_config + restart_stls + ;; + 4) + set_stls_fastopen + write_stls_config + restart_stls + ;; + 5) + set_stls_strict + write_stls_config + restart_stls + ;; + 6) + set_stls_tls_wildcard_sni + write_stls_config + restart_stls + ;; + 7) + set_stls_fallback + write_stls_config + restart_stls + ;; + 8) + manage_stls_dispatch + if [[ ! -z "${stls_dispatch}" ]]; then + write_stls_config + restart_stls + fi + ;; + 9) + set_stls_port + set_stls_password + set_stls_sni + set_stls_fastopen + set_stls_strict + set_stls_tls_wildcard_sni + set_stls_fallback + manage_stls_dispatch + write_stls_config + restart_stls + ;; + *) + echo -e "${Error} 请输入正确的数字(1-9)" + set_stls_config + ;; + esac +} + +# Dispatch 管理的简化版本 +manage_stls_dispatch(){ + echo -e "${Info} 当前 dispatch 配置:" + if [[ -e ${STLS_Conf} ]]; then + echo -e "${Green_font_prefix}$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map(" \(.key) -> \(.value)") | join("\n")')${Font_color_suffix}" + else + echo -e " ${stls_sni} -> 1.1.1.1:443" + echo -e " captive.apple.com -> captive.apple.com:443" + fi + + echo && echo -e "是否需要修改 dispatch 配置? +======================================== +${Green_font_prefix} 1.${Font_color_suffix} 使用默认配置(推荐) +${Green_font_prefix} 2.${Font_color_suffix} 自定义配置所有条目 +========================================" + read -e -p "(默认:1.使用默认):" dispatch_choice + [[ -z "${dispatch_choice}" ]] && dispatch_choice="1" + + if [[ "${dispatch_choice}" == "1" ]]; then + # 使用默认配置,确保使用当前设置的 SNI + echo -e "${Info} 使用默认 dispatch 配置,SNI: ${stls_sni}" + # 不设置 stls_dispatch,让 write_stls_config 使用默认逻辑 + stls_dispatch="" + elif [[ "${dispatch_choice}" == "2" ]]; then + echo -e "${Info} 重新配置 dispatch 条目" + echo "请输入 dispatch 配置 (每行格式: 域名:目标地址,回车结束):" + echo "示例: cloudflare.com:1.1.1.1:443" + + local dispatch_entries="" + local line_count=0 + while true; do + read -e -p "条目 $((line_count+1)) (直接回车结束):" dispatch_entry + if [[ -z "${dispatch_entry}" ]]; then + break + fi + + if [[ "${dispatch_entry}" =~ ^([^:]+):(.+)$ ]]; then + local sni="${BASH_REMATCH[1]}" + local target="${BASH_REMATCH[2]}" + + if [[ ${line_count} -gt 0 ]]; then + dispatch_entries="${dispatch_entries}, + " + fi + dispatch_entries="${dispatch_entries}\"${sni}\": \"${target}\"" + line_count=$((line_count+1)) + else + echo -e "${Error} 格式错误,请使用格式: 域名:目标地址" + fi + done + + if [[ ${line_count} -gt 0 ]]; then + stls_dispatch="${dispatch_entries}" + echo -e "${Info} 已配置 ${line_count} 个 dispatch 条目" + else + echo -e "${Info} 没有输入条目,使用默认 dispatch 配置" + # 不设置 stls_dispatch,让 write_stls_config 使用默认逻辑 + stls_dispatch="" + fi + fi +} + +# 增强版 Dispatch 管理(支持单条增删改) +manage_stls_dispatch_advanced(){ + echo -e "${Info} 当前 dispatch 配置:" + if [[ -e ${STLS_Conf} ]]; then + echo -e "${Green_font_prefix}$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map(" \(.key) -> \(.value)") | join("\n")')${Font_color_suffix}" + else + echo -e " ${stls_sni} -> 1.1.1.1:443" + echo -e " captive.apple.com -> captive.apple.com:443" + fi + + echo && echo -e "请选择 dispatch 管理操作: +======================================== +${Green_font_prefix} 1.${Font_color_suffix} 保持当前配置 +${Green_font_prefix} 2.${Font_color_suffix} 添加新条目 +${Green_font_prefix} 3.${Font_color_suffix} 删除指定条目 +${Green_font_prefix} 4.${Font_color_suffix} 修改指定条目 +${Green_font_prefix} 5.${Font_color_suffix} 重新配置所有条目 +========================================" + read -e -p "(默认:1.保持当前):" dispatch_choice + [[ -z "${dispatch_choice}" ]] && dispatch_choice="1" + + case "${dispatch_choice}" in + 1) + echo -e "${Info} 保持当前配置" + ;; + 2) + add_dispatch_entry + ;; + 3) + delete_dispatch_entry + ;; + 4) + modify_dispatch_entry + ;; + 5) + reconfigure_all_dispatch + ;; + *) + echo -e "${Error} 输入错误,保持当前配置" + ;; + esac +} + +# 添加 dispatch 条目 +add_dispatch_entry(){ + echo -e "${Info} 添加新的 dispatch 条目" + read -e -p "请输入域名 (如: example.com):" new_sni + if [[ -z "${new_sni}" ]]; then + echo -e "${Error} 域名不能为空" + return + fi + + read -e -p "请输入目标地址 (如: 1.1.1.1:443):" new_target + if [[ -z "${new_target}" ]]; then + echo -e "${Error} 目标地址不能为空" + return + fi + + # 获取当前 dispatch 配置 + if [[ -e ${STLS_Conf} ]]; then + current_dispatch=$(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map("\"\(.key)\": \"\(.value)\"") | join(",\n ")') + stls_dispatch="${current_dispatch}, + \"${new_sni}\": \"${new_target}\"" + else + stls_dispatch="\"${stls_sni}\": \"1.1.1.1:443\", + \"captive.apple.com\": \"captive.apple.com:443\", + \"${new_sni}\": \"${new_target}\"" + fi + + echo -e "${Info} 已添加条目: ${new_sni} -> ${new_target}" +} + +# 删除 dispatch 条目 +delete_dispatch_entry(){ + if [[ ! -e ${STLS_Conf} ]]; then + echo -e "${Error} 配置文件不存在" + return + fi + + echo -e "${Info} 当前 dispatch 条目:" + # 显示带编号的列表 + local domains=($(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | keys[]')) + local targets=($(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map(.value) | .[]')) + + if [[ ${#domains[@]} -eq 0 ]]; then + echo -e "${Error} 没有可删除的条目" + return + fi + + for i in "${!domains[@]}"; do + echo -e " ${Green_font_prefix}$((i+1)).${Font_color_suffix} ${domains[i]} -> ${targets[i]}" + done + + read -e -p "请输入要删除的条目编号 (1-${#domains[@]}):" del_num + if [[ -z "${del_num}" ]] || [[ ${del_num} -lt 1 ]] || [[ ${del_num} -gt ${#domains[@]} ]]; then + echo -e "${Error} 输入的编号无效" + return + fi + + # 重新构建 dispatch 配置,排除删除的条目 + local del_domain="${domains[$((del_num-1))]}" + local new_entries="" + local count=0 + + for i in "${!domains[@]}"; do + if [[ "${domains[i]}" != "${del_domain}" ]]; then + if [[ ${count} -gt 0 ]]; then + new_entries="${new_entries}, + " + fi + new_entries="${new_entries}\"${domains[i]}\": \"${targets[i]}\"" + count=$((count+1)) + fi + done + + if [[ ${count} -eq 0 ]]; then + # 如果删除后没有条目,使用默认配置 + stls_dispatch="\"${stls_sni}\": \"1.1.1.1:443\", + \"captive.apple.com\": \"captive.apple.com:443\"" + else + stls_dispatch="${new_entries}" + fi + + echo -e "${Info} 已删除条目: ${del_domain}" +} + +# 修改 dispatch 条目 +modify_dispatch_entry(){ + if [[ ! -e ${STLS_Conf} ]]; then + echo -e "${Error} 配置文件不存在" + return + fi + + echo -e "${Info} 当前 dispatch 条目:" + # 显示带编号的列表 + local domains=($(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | keys[]')) + local targets=($(cat ${STLS_Conf}|jq -r '.server.tls_addr.dispatch | to_entries | map(.value) | .[]')) + + if [[ ${#domains[@]} -eq 0 ]]; then + echo -e "${Error} 没有可修改的条目" + return + fi + + for i in "${!domains[@]}"; do + echo -e " ${Green_font_prefix}$((i+1)).${Font_color_suffix} ${domains[i]} -> ${targets[i]}" + done + + read -e -p "请输入要修改的条目编号 (1-${#domains[@]}):" mod_num + if [[ -z "${mod_num}" ]] || [[ ${mod_num} -lt 1 ]] || [[ ${mod_num} -gt ${#domains[@]} ]]; then + echo -e "${Error} 输入的编号无效" + return + fi + + local mod_domain="${domains[$((mod_num-1))]}" + local mod_target="${targets[$((mod_num-1))]}" + + echo -e "${Info} 当前条目: ${mod_domain} -> ${mod_target}" + read -e -p "请输入新的域名 (直接回车保持不变):" new_domain + read -e -p "请输入新的目标地址 (直接回车保持不变):" new_target + + [[ -z "${new_domain}" ]] && new_domain="${mod_domain}" + [[ -z "${new_target}" ]] && new_target="${mod_target}" + + # 重新构建 dispatch 配置 + local new_entries="" + for i in "${!domains[@]}"; do + if [[ ${i} -gt 0 ]]; then + new_entries="${new_entries}, + " + fi + + if [[ ${i} -eq $((mod_num-1)) ]]; then + new_entries="${new_entries}\"${new_domain}\": \"${new_target}\"" + else + new_entries="${new_entries}\"${domains[i]}\": \"${targets[i]}\"" + fi + done + + stls_dispatch="${new_entries}" + echo -e "${Info} 已修改条目: ${mod_domain} -> ${mod_target} => ${new_domain} -> ${new_target}" +} + +# 重新配置所有 dispatch 条目 +reconfigure_all_dispatch(){ + echo -e "${Info} 重新配置所有 dispatch 条目" + echo "请输入 dispatch 配置 (每行格式: 域名:目标地址,回车结束):" + echo "示例: cloudflare.com:1.1.1.1:443" + + local dispatch_entries="" + local line_count=0 + while true; do + read -e -p "条目 $((line_count+1)) (直接回车结束):" dispatch_entry + if [[ -z "${dispatch_entry}" ]]; then + break + fi + + if [[ "${dispatch_entry}" =~ ^([^:]+):(.+)$ ]]; then + local sni="${BASH_REMATCH[1]}" + local target="${BASH_REMATCH[2]}" + + if [[ ${line_count} -gt 0 ]]; then + dispatch_entries="${dispatch_entries}, + " + fi + dispatch_entries="${dispatch_entries}\"${sni}\": \"${target}\"" + line_count=$((line_count+1)) + else + echo -e "${Error} 格式错误,请使用格式: 域名:目标地址" + fi + done + + if [[ ${line_count} -gt 0 ]]; then + stls_dispatch="${dispatch_entries}" + echo -e "${Info} 已配置 ${line_count} 个 dispatch 条目" + else + echo -e "${Info} 使用默认 dispatch 配置" + stls_dispatch="\"${stls_sni}\": \"1.1.1.1:443\", + \"captive.apple.com\": \"captive.apple.com:443\"" + fi +} + +start(){ + check_installed_status + check_status + if [[ "$status" == "running" ]]; then + echo -e "${Info} Shadowsocks Rust 已在运行!" + else + systemctl start ss-rust + check_status + if [[ "$status" == "running" ]]; then + echo -e "${Info} Shadowsocks Rust 启动成功!" + else + echo -e "${Error} Shadowsocks Rust 启动失败!" + exit 1 + fi + fi + sleep 3s +} + +start_stls(){ + check_stls_installed_status + check_stls_status + if [[ "$stls_status" == "running" ]]; then + echo -e "${Info} Shadow TLS 已在运行!" + else + systemctl start shadowtls + check_stls_status + if [[ "$stls_status" == "running" ]]; then + echo -e "${Info} Shadow TLS 启动成功!" + else + echo -e "${Error} Shadow TLS 启动失败!" + exit 1 + fi + fi + sleep 3s +} + +stop(){ + check_installed_status + check_status + [[ !"$status" == "running" ]] && echo -e "${Error} Shadowsocks Rust 没有运行,请检查!" && exit 1 + systemctl stop ss-rust + sleep 3s + start_menu +} + +stop_stls(){ + check_stls_installed_status + check_stls_status + [[ !"$stls_status" == "running" ]] && echo -e "${Error} Shadow TLS 没有运行,请检查!" && exit 1 + systemctl stop shadowtls + sleep 3s +} + +restart(){ + check_installed_status + systemctl restart ss-rust + echo -e "${Info} Shadowsocks Rust 重启完毕 !" + sleep 3s + start_menu +} + +restart_stls(){ + check_stls_installed_status + systemctl restart shadowtls + echo -e "${Info} Shadow TLS 重启完毕 !" + sleep 3s +} + +update(){ + check_installed_status + check_new_ver + check_ver_comparison + echo -e "${Info} Shadowsocks Rust 更新完毕!" + sleep 3s + start_menu +} + +update_stls(){ + check_stls_installed_status + check_stls_new_ver + check_stls_ver_comparison + echo -e "${Info} Shadow TLS 更新完毕!" + sleep 3s +} + +# 脚本更新函数 +update_sh(){ + echo -e "当前版本为 [ ${sh_ver} ],开始检测最新版本..." + sh_new_ver=$(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/xOS/Shadowsocks-Rust/master/ss-rust.sh"|grep 'sh_ver="'|awk -F "=" '{print $NF}'|sed 's/\"//g'|head -1) + [[ -z ${sh_new_ver} ]] && echo -e "${Error} 检测最新版本失败 !" && start_menu + if [[ ${sh_new_ver} != ${sh_ver} ]]; then + echo -e "发现新版本[ ${sh_new_ver} ],是否更新?[Y/n]" + read -p "(默认:y):" yn + [[ -z "${yn}" ]] && yn="y" + if [[ ${yn} == [Yy] ]]; then + wget -O ss-rust.sh --no-check-certificate https://raw.githubusercontent.com/xOS/Shadowsocks-Rust/master/ss-rust.sh && chmod +x ss-rust.sh + echo -e "脚本已更新为最新版本[ ${sh_new_ver} ]!" + echo -e "3s后执行新脚本" + sleep 3s + bash ss-rust.sh + else + echo && echo " 已取消..." && echo + sleep 3s + start_menu + fi + else + echo -e "当前已是最新版本[ ${sh_new_ver} ] !" + sleep 3s + start_menu + fi + sleep 3s + bash ss-rust.sh +} + +uninstall(){ + check_installed_status + echo "确定要卸载 Shadowsocks Rust ? (y/N)" + echo + read -e -p "(默认:n):" unyn + [[ -z ${unyn} ]] && unyn="n" + if [[ ${unyn} == [Yy] ]]; then + check_status + [[ "$status" == "running" ]] && systemctl stop ss-rust + systemctl disable ss-rust + rm -rf "${SS_Folder}" + rm -rf "${SS_File}" + echo && echo "Shadowsocks Rust 卸载完成!" && echo + else + echo && echo "卸载已取消..." && echo + fi + sleep 3s + start_menu +} + +uninstall_stls(){ + check_stls_installed_status + echo "确定要卸载 Shadow TLS ? (y/N)" + echo + read -e -p "(默认:n):" unyn + [[ -z ${unyn} ]] && unyn="n" + if [[ ${unyn} == [Yy] ]]; then + check_stls_status + [[ "$stls_status" == "running" ]] && systemctl stop shadowtls + systemctl disable shadowtls + rm -rf "${STLS_Folder}" + rm -rf "${STLS_File}" + rm -f "/etc/systemd/system/shadowtls.service" + systemctl daemon-reload + echo && echo "Shadow TLS 卸载完成!" && echo + else + echo && echo "卸载已取消..." && echo + fi + sleep 3s +} + +getipv4(){ + ipv4=$(wget -qO- -4 -t1 -T2 ipinfo.io/ip) + if [[ -z "${ipv4}" ]]; then + ipv4=$(wget -qO- -4 -t1 -T2 api.ip.sb/ip) + if [[ -z "${ipv4}" ]]; then + ipv4=$(wget -qO- -4 -t1 -T2 members.3322.org/dyndns/getip) + if [[ -z "${ipv4}" ]]; then + ipv4="IPv4_Error" + fi + fi + fi +} + +getipv6(){ + ipv6=$(wget -qO- -6 -t1 -T2 ifconfig.co) + if [[ -z "${ipv6}" ]]; then + ipv6="IPv6_Error" + fi +} + +urlsafe_base64(){ + date=$(echo -n "$1"|base64|sed ':a;N;s/\n/ /g;ta'|sed 's/ //g;s/=//g;s/+/-/g;s/\//_/g') + echo -e "${date}" +} + +link_qr(){ + if [[ "${ipv4}" != "IPv4_Error" ]]; then + SSbase64=$(urlsafe_base64 "${cipher}:${password}@${ipv4}:${port}") + SSurl="ss://${SSbase64}" + SSQRcode="https://cli.im/api/qrcode/code?text=${SSurl}" + link_ipv4=" 链接 [IPv4]:${Red_font_prefix}${SSurl}${Font_color_suffix} \n 二维码[IPv4]:${Red_font_prefix}${SSQRcode}${Font_color_suffix}" + fi + if [[ "${ipv6}" != "IPv6_Error" ]]; then + SSbase64=$(urlsafe_base64 "${cipher}:${password}@${ipv6}:${port}") + SSurl="ss://${SSbase64}" + SSQRcode="https://cli.im/api/qrcode/code?text=${SSurl}" + link_ipv6=" 链接 [IPv6]:${Red_font_prefix}${SSurl}${Font_color_suffix} \n 二维码[IPv6]:${Red_font_prefix}${SSQRcode}${Font_color_suffix}" + fi +} + +before_start_menu(){ + echo && echo -n " 任意键继续..." && read + start_menu +} + +before_shadowtls_menu(){ + echo && echo -n " 任意键继续..." && read + shadowtls_menu +} + +# Shadow TLS 状态和版本检查函数 +check_stls_installed_status(){ + [[ ! -e ${STLS_File} ]] && echo -e "${Error} Shadow TLS 没有安装,请检查!" && exit 1 +} + +check_stls_status(){ + stls_status=`systemctl status shadowtls | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1` +} + +check_stls_new_ver(){ + stls_new_ver=$(wget -qO- https://api.github.com/repos/ihciah/shadow-tls/releases| jq -r '[.[] | select(.prerelease == false) | select(.draft == false) | .tag_name] | .[0]') + [[ -z ${stls_new_ver} ]] && echo -e "${Error} Shadow TLS 最新版本获取失败!" && exit 1 + echo -e "${Info} 检测到 Shadow TLS 最新版本为 [ ${stls_new_ver} ]" +} + +check_stls_ver_comparison(){ + stls_now_ver=$(cat ${STLS_Now_ver_File}) + if [[ "${stls_now_ver}" != "${stls_new_ver}" ]]; then + echo -e "${Info} 发现 Shadow TLS 已有新版本 [ ${stls_new_ver} ],旧版本 [ ${stls_now_ver} ]" + read -e -p "是否更新 ? [Y/n]:" yn + [[ -z "${yn}" ]] && yn="y" + if [[ $yn == [Yy] ]]; then + check_stls_status + \cp "${STLS_Conf}" "/tmp/stls_config.json" + download_stls + mv -f "/tmp/stls_config.json" "${STLS_Conf}" + restart_stls + fi + else + echo -e "${Info} 当前 Shadow TLS 已是最新版本 [ ${stls_new_ver} ] !" && exit 1 + fi +} + +# Shadow TLS 专用菜单 +shadowtls_menu(){ + check_root + check_sys + sys_arch + + # 检查 Shadow TLS 安装状态 + if [[ -e ${STLS_File} ]]; then + check_stls_status + if [[ "$stls_status" == "running" ]]; then + stls_status_show="${Green_font_prefix}已安装${Font_color_suffix} 且 ${Green_font_prefix}运行中${Font_color_suffix}" + else + stls_status_show="${Green_font_prefix}已安装${Font_color_suffix} 但 ${Yellow_font_prefix}未运行${Font_color_suffix}" + fi + else + stls_status_show="${Red_font_prefix}未安装${Font_color_suffix}" + fi + + clear + echo -e "Shadow TLS 管理菜单 ${Red_font_prefix}[v${sh_ver}]${Font_color_suffix} + +==================状态================== + Shadow TLS : [${stls_status_show}] +======================================== + ${Green_font_prefix}0.${Font_color_suffix} 更新脚本 +==================菜单================== + ${Green_font_prefix}1.${Font_color_suffix} 安装 Shadow TLS + ${Green_font_prefix}2.${Font_color_suffix} 更新 Shadow TLS + ${Green_font_prefix}3.${Font_color_suffix} 卸载 Shadow TLS +———————————————————————————————————————— + ${Green_font_prefix}4.${Font_color_suffix} 启动 Shadow TLS + ${Green_font_prefix}5.${Font_color_suffix} 停止 Shadow TLS + ${Green_font_prefix}6.${Font_color_suffix} 重启 Shadow TLS +———————————————————————————————————————— + ${Green_font_prefix}7.${Font_color_suffix} 修改 Shadow TLS 配置 + ${Green_font_prefix}8.${Font_color_suffix} 查看 Shadow TLS 配置 + ${Green_font_prefix}9.${Font_color_suffix} 查看 Shadow TLS 状态 +———————————————————————————————————————— + ${Green_font_prefix}10.${Font_color_suffix} 查看完整配置信息 + ${Green_font_prefix}11.${Font_color_suffix} 返回主菜单 +———————————————————————————————————————— + ${Green_font_prefix}00.${Font_color_suffix} 退出脚本 +========================================" && echo + + read -e -p " 请输入数字 [0-11]:" stls_num + case "$stls_num" in + 1) + install_stls + shadowtls_menu + ;; + 2) + update_stls + shadowtls_menu + ;; + 3) + uninstall_stls + shadowtls_menu + ;; + 4) + start_stls + shadowtls_menu + ;; + 5) + stop_stls + shadowtls_menu + ;; + 6) + restart_stls + shadowtls_menu + ;; + 7) + set_stls_config + shadowtls_menu + ;; + 8) + view_stls_only + ;; + 9) + view_stls_status + ;; + 10) + view_combined_config shadowtls + ;; + 11) + start_menu + ;; + 0) + update_sh + ;; + 00) + exit 1 + ;; + *) + echo -e "${Error} 请输入正确数字 [0-11] (退出输入00)" + sleep 5s + shadowtls_menu + ;; + esac +} + +# 查看 Shadowsocks Rust 状态函数 +view_ss_status(){ + check_installed_status + + echo -e "${Info} 正在获取 Shadowsocks Rust 状态信息..." + echo + echo "==================================" + echo -e " Shadowsocks Rust 服务状态" + echo "==================================" + + systemctl status ss-rust + + echo "==================================" + echo + read -e -p "按回车键返回主菜单..." + start_menu +} + +# 查看 Shadow TLS 状态函数 +view_stls_status(){ + check_stls_installed_status + + echo -e "${Info} 正在获取 Shadow TLS 状态信息..." + echo + echo "==================================" + echo -e " Shadow TLS 服务状态" + echo "==================================" + + systemctl status shadowtls + + echo "==================================" + echo + read -e -p "按回车键返回 Shadow TLS 菜单..." + shadowtls_menu +} + +# 查看 Shadow TLS 配置函数 +view_stls_only(){ + check_stls_installed_status + echo -e "${Info} 正在获取 Shadow TLS 配置信息..." + echo + echo "==================================" + echo -e " Shadow TLS 配置信息" + echo "==================================" + + if [[ -f "$STLS_Conf" ]]; then + cat "$STLS_Conf" + else + echo -e "${Error} Shadow TLS 配置文件不存在!" + fi + + echo "==================================" + echo + read -e -p "按回车键返回 Shadow TLS 菜单..." + shadowtls_menu +} + +# 主菜单函数 +start_menu(){ + echo -e "${Info} 正在启动 Shadowsocks Rust 管理脚本..." + check_root + echo -e "${Info} 权限检查完成,正在检测系统..." + check_sys + echo -e "${Info} 系统检测完成,正在检测架构..." + sys_arch + echo -e "${Info} 架构检测完成,正在检查服务状态..." + + # 检查安装状态 + if [[ -e ${SS_File} ]]; then + check_status + if [[ "$status" == "running" ]]; then + ss_status_show="${Green_font_prefix}已安装${Font_color_suffix} 且 ${Green_font_prefix}运行中${Font_color_suffix}" + else + ss_status_show="${Green_font_prefix}已安装${Font_color_suffix} 但 ${Yellow_font_prefix}未运行${Font_color_suffix}" + fi + else + ss_status_show="${Red_font_prefix}未安装${Font_color_suffix}" + fi + + # 检查 Shadow TLS 安装状态 + if [[ -e ${STLS_File} ]]; then + check_stls_status + if [[ "$stls_status" == "running" ]]; then + stls_status_show="${Green_font_prefix}已安装${Font_color_suffix} 且 ${Green_font_prefix}运行中${Font_color_suffix}" + else + stls_status_show="${Green_font_prefix}已安装${Font_color_suffix} 但 ${Yellow_font_prefix}未运行${Font_color_suffix}" + fi + else + stls_status_show="${Red_font_prefix}未安装${Font_color_suffix}" + fi + + clear + echo -e "Shadowsocks Rust 管理脚本 ${Red_font_prefix}[v${sh_ver}]${Font_color_suffix} + +==================状态================== + Shadowsocks Rust : [${ss_status_show}] + Shadow TLS : [${stls_status_show}] +======================================== + ${Green_font_prefix}0.${Font_color_suffix} 更新脚本 +==================菜单================== + ${Green_font_prefix}1.${Font_color_suffix} 安装 Shadowsocks Rust + ${Green_font_prefix}2.${Font_color_suffix} 更新 Shadowsocks Rust + ${Green_font_prefix}3.${Font_color_suffix} 卸载 Shadowsocks Rust +———————————————————————————————————————— + ${Green_font_prefix}4.${Font_color_suffix} 启动 Shadowsocks Rust + ${Green_font_prefix}5.${Font_color_suffix} 停止 Shadowsocks Rust + ${Green_font_prefix}6.${Font_color_suffix} 重启 Shadowsocks Rust +———————————————————————————————————————— + ${Green_font_prefix}7.${Font_color_suffix} 配置 Shadowsocks Rust 相关 + ${Green_font_prefix}8.${Font_color_suffix} 查看 Shadowsocks Rust 配置 + ${Green_font_prefix}9.${Font_color_suffix} 查看 Shadowsocks Rust 状态 +======================================== + ${Green_font_prefix}10.${Font_color_suffix} 配置 Shadow TLS 相关 + ${Green_font_prefix}11.${Font_color_suffix} 查看完整配置信息 +———————————————————————————————————————— + ${Green_font_prefix}00.${Font_color_suffix} 退出脚本 +========================================" && echo + read -e -p " 请输入数字 [0-11]:" num + case "$num" in + 1) + install + ;; + 2) + update + start_menu + ;; + 3) + uninstall + start_menu + ;; + 4) + start + start_menu + ;; + 5) + stop + start_menu + ;; + 6) + restart + start_menu + ;; + 7) + set_config + ;; + 8) + view + ;; + 9) + view_ss_status + ;; + 10) + shadowtls_menu + ;; + 11) + view_combined_config + ;; + 0) + update_sh + ;; + 00) + exit 1 + ;; + *) + echo -e "${Error} 请输入正确数字 [0-11] (退出输入00)" + sleep 5s + start_menu + ;; + esac +} + +# 脚本执行入口 +start_menu