#!/bin/bash # ========================= # 老王sing-box四合一安装脚本 # vless-version-reality|vmess-ws-tls(tunnel)|hysteria2|tuic5 # 最后更新时间: 2025.10.17 # ========================= export LANG=en_US.UTF-8 # 定义颜色 re="\033[0m" red="\033[1;91m" green="\e[1;32m" yellow="\e[1;33m" purple="\e[1;35m" skyblue="\e[1;36m" red() { echo -e "\e[1;91m$1\033[0m"; } green() { echo -e "\e[1;32m$1\033[0m"; } yellow() { echo -e "\e[1;33m$1\033[0m"; } purple() { echo -e "\e[1;35m$1\033[0m"; } skyblue() { echo -e "\e[1;36m$1\033[0m"; } reading() { read -p "$(red "$1")" "$2"; } # 定义常量 server_name="sing-box" work_dir="/etc/sing-box" config_dir="${work_dir}/config.json" client_dir="${work_dir}/url.txt" export vless_port=${PORT:-$(shuf -i 1000-65000 -n 1)} export CFIP=${CFIP:-'cf.877774.xyz'} export CFPORT=${CFPORT:-'443'} # 检查是否为root下运行 [[ $EUID -ne 0 ]] && red "请在root用户下运行脚本" && exit 1 # 检查命令是否存在函数 command_exists() { command -v "$1" >/dev/null 2>&1 } # 检查服务状态通用函数 check_service() { local service_name=$1 local service_file=$2 [[ ! -f "${service_file}" ]] && { red "not installed"; return 2; } if command_exists apk; then rc-service "${service_name}" status | grep -q "started" && green "running" || yellow "not running" else systemctl is-active "${service_name}" | grep -q "^active$" && green "running" || yellow "not running" fi return $? } # 检查sing-box状态 check_singbox() { check_service "sing-box" "${work_dir}/${server_name}" } # 检查argo状态 check_argo() { check_service "argo" "${work_dir}/argo" } # 检查nginx状态 check_nginx() { command_exists nginx || { red "not installed"; return 2; } check_service "nginx" "$(command -v nginx)" } #根据系统类型安装、卸载依赖 manage_packages() { if [ $# -lt 2 ]; then red "Unspecified package name or action" return 1 fi action=$1 shift for package in "$@"; do if [ "$action" == "install" ]; then if command_exists "$package"; then green "${package} already installed" continue fi yellow "正在安装 ${package}..." if command_exists apt; then DEBIAN_FRONTEND=noninteractive apt install -y "$package" elif command_exists dnf; then dnf install -y "$package" elif command_exists yum; then yum install -y "$package" elif command_exists apk; then apk update apk add "$package" else red "Unknown system!" return 1 fi elif [ "$action" == "uninstall" ]; then if ! command_exists "$package"; then yellow "${package} is not installed" continue fi yellow "正在卸载 ${package}..." if command_exists apt; then apt remove -y "$package" && apt autoremove -y elif command_exists dnf; then dnf remove -y "$package" && dnf autoremove -y elif command_exists yum; then yum remove -y "$package" && yum autoremove -y elif command_exists apk; then apk del "$package" else red "Unknown system!" return 1 fi else red "Unknown action: $action" return 1 fi done return 0 } # 获取ip get_realip() { ip=$(curl -4 -sm 2 ip.sb) ipv6() { curl -6 -sm 2 ip.sb; } if [ -z "$ip" ]; then echo "[$(ipv6)]" elif curl -4 -sm 2 http://ipinfo.io/org | grep -qE 'Cloudflare|UnReal|AEZA|Andrei'; then echo "[$(ipv6)]" else resp=$(curl -sm 8 "https://status.eooce.com/api/$ip" | jq -r '.status') if [ "$resp" = "Available" ]; then echo "$ip" else v6=$(ipv6) [ -n "$v6" ] && echo "[$v6]" || echo "$ip" fi fi } # 处理防火墙 allow_port() { has_ufw=0 has_firewalld=0 has_iptables=0 has_ip6tables=0 command_exists ufw && has_ufw=1 command_exists firewall-cmd && systemctl is-active firewalld >/dev/null 2>&1 && has_firewalld=1 command_exists iptables && has_iptables=1 command_exists ip6tables && has_ip6tables=1 # 出站和基础规则 [ "$has_ufw" -eq 1 ] && ufw --force default allow outgoing >/dev/null 2>&1 [ "$has_firewalld" -eq 1 ] && firewall-cmd --permanent --zone=public --set-target=ACCEPT >/dev/null 2>&1 [ "$has_iptables" -eq 1 ] && { iptables -C INPUT -i lo -j ACCEPT 2>/dev/null || iptables -I INPUT 3 -i lo -j ACCEPT iptables -C INPUT -p icmp -j ACCEPT 2>/dev/null || iptables -I INPUT 4 -p icmp -j ACCEPT iptables -P FORWARD DROP 2>/dev/null || true iptables -P OUTPUT ACCEPT 2>/dev/null || true } [ "$has_ip6tables" -eq 1 ] && { ip6tables -C INPUT -i lo -j ACCEPT 2>/dev/null || ip6tables -I INPUT 3 -i lo -j ACCEPT ip6tables -C INPUT -p icmp -j ACCEPT 2>/dev/null || ip6tables -I INPUT 4 -p icmp -j ACCEPT ip6tables -P FORWARD DROP 2>/dev/null || true ip6tables -P OUTPUT ACCEPT 2>/dev/null || true } # 入站 for rule in "$@"; do port=${rule%/*} proto=${rule#*/} [ "$has_ufw" -eq 1 ] && ufw allow in ${port}/${proto} >/dev/null 2>&1 [ "$has_firewalld" -eq 1 ] && firewall-cmd --permanent --add-port=${port}/${proto} >/dev/null 2>&1 [ "$has_iptables" -eq 1 ] && (iptables -C INPUT -p ${proto} --dport ${port} -j ACCEPT 2>/dev/null || iptables -I INPUT 4 -p ${proto} --dport ${port} -j ACCEPT) [ "$has_ip6tables" -eq 1 ] && (ip6tables -C INPUT -p ${proto} --dport ${port} -j ACCEPT 2>/dev/null || ip6tables -I INPUT 4 -p ${proto} --dport ${port} -j ACCEPT) done [ "$has_firewalld" -eq 1 ] && firewall-cmd --reload >/dev/null 2>&1 # 规则持久化 if command_exists rc-service 2>/dev/null; then [ "$has_iptables" -eq 1 ] && iptables-save > /etc/iptables/rules.v4 2>/dev/null [ "$has_ip6tables" -eq 1 ] && ip6tables-save > /etc/iptables/rules.v6 2>/dev/null else if ! command_exists netfilter-persistent; then manage_packages install iptables-persistent || yellow "请手动安装netfilter-persistent或保存iptables规则" netfilter-persistent save >/dev/null 2>&1 elif command_exists service; then service iptables save 2>/dev/null service ip6tables save 2>/dev/null fi fi } # 下载并安装 sing-box,cloudflared install_singbox() { clear purple "正在安装sing-box中,请稍后..." # 判断系统架构 ARCH_RAW=$(uname -m) case "${ARCH_RAW}" in 'x86_64') ARCH='amd64' ;; 'x86' | 'i686' | 'i386') ARCH='386' ;; 'aarch64' | 'arm64') ARCH='arm64' ;; 'armv7l') ARCH='armv7' ;; 's390x') ARCH='s390x' ;; *) red "不支持的架构: ${ARCH_RAW}"; exit 1 ;; esac # 下载sing-box,cloudflared [ ! -d "${work_dir}" ] && mkdir -p "${work_dir}" && chmod 777 "${work_dir}" # latest_version=$(curl -s "https://api.github.com/repos/SagerNet/sing-box/releases" | jq -r '[.[] | select(.prerelease==false)][0].tag_name | sub("^v"; "")') # curl -sLo "${work_dir}/${server_name}.tar.gz" "https://github.com/SagerNet/sing-box/releases/download/v${latest_version}/sing-box-${latest_version}-linux-${ARCH}.tar.gz" # curl -sLo "${work_dir}/qrencode" "https://github.com/eooce/test/releases/download/${ARCH}/qrencode-linux-${ARCH}" curl -sLo "${work_dir}/qrencode" "https://$ARCH.ssss.nyc.mn/qrencode" curl -sLo "${work_dir}/sing-box" "https://$ARCH.ssss.nyc.mn/sbx" curl -sLo "${work_dir}/argo" "https://$ARCH.ssss.nyc.mn/bot" # tar -xzvf "${work_dir}/${server_name}.tar.gz" -C "${work_dir}/" && \ # mv "${work_dir}/sing-box-${latest_version}-linux-${ARCH}/sing-box" "${work_dir}/" && \ # rm -rf "${work_dir}/${server_name}.tar.gz" "${work_dir}/sing-box-${latest_version}-linux-${ARCH}" chown root:root ${work_dir} && chmod +x ${work_dir}/${server_name} ${work_dir}/argo ${work_dir}/qrencode # 生成随机端口和密码 nginx_port=$(($vless_port + 1)) tuic_port=$(($vless_port + 2)) hy2_port=$(($vless_port + 3)) uuid=$(cat /proc/sys/kernel/random/uuid) password=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c 24) output=$(/etc/sing-box/sing-box generate reality-keypair) private_key=$(echo "${output}" | awk '/PrivateKey:/ {print $2}') public_key=$(echo "${output}" | awk '/PublicKey:/ {print $2}') # 放行端口 allow_port $vless_port/tcp $nginx_port/tcp $tuic_port/udp $hy2_port/udp > /dev/null 2>&1 # 生成自签名证书 openssl ecparam -genkey -name prime256v1 -out "${work_dir}/private.key" openssl req -new -x509 -days 3650 -key "${work_dir}/private.key" -out "${work_dir}/cert.pem" -subj "/CN=bing.com" # 检测网络类型并设置DNS策略 dns_strategy=$(ping -c 1 -W 3 8.8.8.8 >/dev/null 2>&1 && echo "prefer_ipv4" || (ping -c 1 -W 3 2001:4860:4860::8888 >/dev/null 2>&1 && echo "prefer_ipv6" || echo "prefer_ipv4")) # 生成配置文件 cat > "${config_dir}" << EOF { "log": { "disabled": false, "level": "error", "output": "$work_dir/sb.log", "timestamp": true }, "dns": { "servers": [ { "tag": "local", "address": "local", "strategy": "$dns_strategy" } ] }, "ntp": { "enabled": true, "server": "time.apple.com", "server_port": 123, "interval": "30m" }, "inbounds": [ { "type": "vless", "tag": "vless-reality", "listen": "::", "listen_port": $vless_port, "users": [ { "uuid": "$uuid", "flow": "xtls-rprx-vision" } ], "tls": { "enabled": true, "server_name": "www.iij.ad.jp", "reality": { "enabled": true, "handshake": { "server": "www.iij.ad.jp", "server_port": 443 }, "private_key": "$private_key", "short_id": [""] } } }, { "type": "vmess", "tag": "vmess-ws", "listen": "::", "listen_port": 8001, "users": [ { "uuid": "$uuid" } ], "transport": { "type": "ws", "path": "/vmess-argo", "early_data_header_name": "Sec-WebSocket-Protocol" } }, { "type": "hysteria2", "tag": "hysteria2", "listen": "::", "listen_port": $hy2_port, "users": [ { "password": "$uuid" } ], "ignore_client_bandwidth": false, "masquerade": "https://bing.com", "tls": { "enabled": true, "alpn": ["h3"], "min_version": "1.3", "max_version": "1.3", "certificate_path": "$work_dir/cert.pem", "key_path": "$work_dir/private.key" } }, { "type": "tuic", "tag": "tuic", "listen": "::", "listen_port": $tuic_port, "users": [ { "uuid": "$uuid", "password": "$password" } ], "congestion_control": "bbr", "tls": { "enabled": true, "alpn": ["h3"], "certificate_path": "$work_dir/cert.pem", "key_path": "$work_dir/private.key" } } ], "outbounds": [ { "type": "direct", "tag": "direct" }, { "type": "block", "tag": "block" }, { "type": "wireguard", "tag": "wireguard-out", "server": "engage.cloudflareclient.com", "server_port": 2408, "local_address": [ "172.16.0.2/32", "2606:4700:110:851f:4da3:4e2c:cdbf:2ecf/128" ], "private_key": "eAx8o6MJrH4KE7ivPFFCa4qvYw5nJsYHCBQXPApQX1A=", "peer_public_key": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", "reserved": [82, 90, 51], "mtu": 1420 } ], "route": { "rule_set": [ { "tag": "openai", "type": "remote", "format": "binary", "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo-lite/geosite/openai.srs", "download_detour": "direct" }, { "tag": "netflix", "type": "remote", "format": "binary", "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo-lite/geosite/netflix.srs", "download_detour": "direct" } ], "rules": [ { "rule_set": ["openai", "netflix"], "outbound": "wireguard-out" } ], "final": "direct" } } EOF } # debian/ubuntu/centos 守护进程 main_systemd_services() { cat > /etc/systemd/system/sing-box.service << EOF [Unit] Description=sing-box service Documentation=https://sing-box.sagernet.org After=network.target nss-lookup.target [Service] User=root WorkingDirectory=/etc/sing-box CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW ExecStart=/etc/sing-box/sing-box run -c /etc/sing-box/config.json ExecReload=/bin/kill -HUP \$MAINPID Restart=on-failure RestartSec=10 LimitNOFILE=infinity [Install] WantedBy=multi-user.target EOF cat > /etc/systemd/system/argo.service << EOF [Unit] Description=Cloudflare Tunnel After=network.target [Service] Type=simple NoNewPrivileges=yes TimeoutStartSec=0 ExecStart=/bin/sh -c "/etc/sing-box/argo tunnel --url http://localhost:8001 --no-autoupdate --edge-ip-version auto --protocol http2 > /etc/sing-box/argo.log 2>&1" Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target EOF if [ -f /etc/centos-release ]; then yum install -y chrony systemctl start chronyd systemctl enable chronyd chronyc -a makestep yum update -y ca-certificates bash -c 'echo "0 0" > /proc/sys/net/ipv4/ping_group_range' fi systemctl daemon-reload systemctl enable sing-box systemctl start sing-box systemctl enable argo systemctl start argo } # 适配alpine 守护进程 alpine_openrc_services() { cat > /etc/init.d/sing-box << 'EOF' #!/sbin/openrc-run description="sing-box service" command="/etc/sing-box/sing-box" command_args="run -c /etc/sing-box/config.json" command_background=true pidfile="/var/run/sing-box.pid" EOF cat > /etc/init.d/argo << 'EOF' #!/sbin/openrc-run description="Cloudflare Tunnel" command="/bin/sh" command_args="-c '/etc/sing-box/argo tunnel --url http://localhost:8001 --no-autoupdate --edge-ip-version auto --protocol http2 > /etc/sing-box/argo.log 2>&1'" command_background=true pidfile="/var/run/argo.pid" EOF chmod +x /etc/init.d/sing-box chmod +x /etc/init.d/argo rc-update add sing-box default > /dev/null 2>&1 rc-update add argo default > /dev/null 2>&1 } # 生成节点和订阅链接 get_info() { yellow "\nip检测中,请稍等...\n" server_ip=$(get_realip) clear isp=$(curl -s --max-time 2 https://speed.cloudflare.com/meta | awk -F\" '{print $26"-"$18}' | sed -e 's/ /_/g' || echo "vps") if [ -f "${work_dir}/argo.log" ]; then for i in {1..5}; do purple "第 $i 次尝试获取ArgoDoamin中..." argodomain=$(sed -n 's|.*https://\([^/]*trycloudflare\.com\).*|\1|p' "${work_dir}/argo.log") [ -n "$argodomain" ] && break sleep 2 done else restart_argo sleep 6 argodomain=$(sed -n 's|.*https://\([^/]*trycloudflare\.com\).*|\1|p' "${work_dir}/argo.log") fi green "\nArgoDomain:${purple}$argodomain${re}\n" VMESS="{ \"v\": \"2\", \"ps\": \"${isp}\", \"add\": \"${CFIP}\", \"port\": \"${CFPORT}\", \"id\": \"${uuid}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"${argodomain}\", \"path\": \"/vmess-argo?ed=2560\", \"tls\": \"tls\", \"sni\": \"${argodomain}\", \"alpn\": \"\", \"fp\": \"firefox\", \"allowlnsecure\": \"flase\"}" cat > ${work_dir}/url.txt < ${work_dir}/sub.txt chmod 644 ${work_dir}/sub.txt yellow "\n温馨提醒:需打开V2rayN或其他软件里的 "跳过证书验证",或将节点的Insecure或TLS里设置为"true"\n" green "V2rayN,Shadowrocket,Nekobox,Loon,Karing,Sterisand订阅链接:http://${server_ip}:${nginx_port}/${password}\n" $work_dir/qrencode "http://${server_ip}:${nginx_port}/${password}" yellow "\n==========================================================================================" green "\n\nClash,Mihomo系列订阅链接:https://sublink.eooce.com/clash?config=http://${server_ip}:${nginx_port}/${password}\n" $work_dir/qrencode "https://sublink.eooce.com/clash?config=http://${server_ip}:${nginx_port}/${password}" yellow "\n==========================================================================================" green "\n\nSing-box订阅链接:https://sublink.eooce.com/singbox?config=http://${server_ip}:${nginx_port}/${password}\n" $work_dir/qrencode "https://sublink.eooce.com/singbox?config=http://${server_ip}:${nginx_port}/${password}" yellow "\n==========================================================================================" green "\n\nSurge订阅链接:https://sublink.eooce.com/surge?config=http://${server_ip}:${nginx_port}/${password}\n" $work_dir/qrencode "https://sublink.eooce.com/surge?config=http://${server_ip}:${nginx_port}/${password}" yellow "\n==========================================================================================\n" } # nginx订阅配置 add_nginx_conf() { if ! command_exists nginx; then red "nginx未安装,无法配置订阅服务" return 1 else manage_service "nginx" "stop" > /dev/null 2>&1 pkill nginx > /dev/null 2>&1 fi mkdir -p /etc/nginx/conf.d [[ -f "/etc/nginx/conf.d/sing-box.conf" ]] && cp /etc/nginx/conf.d/sing-box.conf /etc/nginx/conf.d/sing-box.conf.bak.sb cat > /etc/nginx/conf.d/sing-box.conf << EOF # sing-box 订阅配置 server { listen $nginx_port; listen [::]:$nginx_port; server_name _; # 安全设置 add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; location = /$password { alias /etc/sing-box/sub.txt; default_type 'text/plain; charset=utf-8'; add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } location / { return 404; } # 禁止访问隐藏文件 location ~ /\. { deny all; access_log off; log_not_found off; } } EOF # 检查主配置文件是否存在 if [ -f "/etc/nginx/nginx.conf" ]; then cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.sb > /dev/null 2>&1 sed -i -e '15{/include \/etc\/nginx\/modules\/\*\.conf/d;}' -e '18{/include \/etc\/nginx\/conf\.d\/\*\.conf/d;}' /etc/nginx/nginx.conf > /dev/null 2>&1 # 检查是否已包含配置目录 if ! grep -q "include.*conf.d" /etc/nginx/nginx.conf; then http_end_line=$(grep -n "^}" /etc/nginx/nginx.conf | tail -1 | cut -d: -f1) if [ -n "$http_end_line" ]; then sed -i "${http_end_line}i \ include /etc/nginx/conf.d/*.conf;" /etc/nginx/nginx.conf > /dev/null 2>&1 fi fi else cat > /etc/nginx/nginx.conf << EOF user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; } EOF fi # 检查nginx配置语法 if nginx -t > /dev/null 2>&1; then if nginx -s reload > /dev/null 2>&1; then green "nginx订阅配置已加载" else start_nginx > /dev/null 2>&1 fi else yellow "nginx配置失败,订阅不可应,但不影响节点使用, issues反馈: https://github.com/eooce/Sing-box/issues" restart_nginx > /dev/null 2>&1 if [ $? -eq 0 ]; then green "nginx订阅配置已生效" else [[ -f "/etc/nginx/nginx.conf.bak.sb" ]] && cp "/etc/nginx/nginx.conf.bak.sb" /etc/nginx/nginx.conf > /dev/null 2>&1 restart_nginx > /dev/null 2>&1 fi fi } # 通用服务管理函数 manage_service() { local service_name="$1" local action="$2" if [ -z "$service_name" ] || [ -z "$action" ]; then red "缺少服务名或操作参数\n" return 1 fi local status=$(check_service "$service_name" 2>/dev/null) case "$action" in "start") if [ "$status" == "running" ]; then yellow "${service_name} 正在运行\n" return 0 elif [ "$status" == "not installed" ]; then yellow "${service_name} 尚未安装!\n" return 1 else yellow "正在启动 ${service_name} 服务\n" if command_exists rc-service; then rc-service "$service_name" start elif command_exists systemctl; then systemctl daemon-reload systemctl start "$service_name" fi if [ $? -eq 0 ]; then green "${service_name} 服务已成功启动\n" return 0 else red "${service_name} 服务启动失败\n" return 1 fi fi ;; "stop") if [ "$status" == "not installed" ]; then yellow "${service_name} 尚未安装!\n" return 2 elif [ "$status" == "not running" ]; then yellow "${service_name} 未运行\n" return 1 else yellow "正在停止 ${service_name} 服务\n" if command_exists rc-service; then rc-service "$service_name" stop elif command_exists systemctl; then systemctl stop "$service_name" fi if [ $? -eq 0 ]; then green "${service_name} 服务已成功停止\n" return 0 else red "${service_name} 服务停止失败\n" return 1 fi fi ;; "restart") if [ "$status" == "not installed" ]; then yellow "${service_name} 尚未安装!\n" return 1 else yellow "正在重启 ${service_name} 服务\n" if command_exists rc-service; then rc-service "$service_name" restart elif command_exists systemctl; then systemctl daemon-reload systemctl restart "$service_name" fi if [ $? -eq 0 ]; then green "${service_name} 服务已成功重启\n" return 0 else red "${service_name} 服务重启失败\n" return 1 fi fi ;; *) red "无效的操作: $action\n" red "可用操作: start, stop, restart\n" return 1 ;; esac } # 启动 sing-box start_singbox() { manage_service "sing-box" "start" } # 停止 sing-box stop_singbox() { manage_service "sing-box" "stop" } # 重启 sing-box restart_singbox() { manage_service "sing-box" "restart" } # 启动 argo start_argo() { manage_service "argo" "start" } # 停止 argo stop_argo() { manage_service "argo" "stop" } # 重启 argo restart_argo() { manage_service "argo" "restart" } # 启动 nginx start_nginx() { manage_service "nginx" "start" } # 重启 nginx restart_nginx() { manage_service "nginx" "restart" } # 卸载 sing-box uninstall_singbox() { reading "确定要卸载 sing-box 吗? (y/n): " choice case "${choice}" in y|Y) yellow "正在卸载 sing-box" if command_exists rc-service; then rc-service sing-box stop rc-service argo stop rm /etc/init.d/sing-box /etc/init.d/argo rc-update del sing-box default rc-update del argo default else # 停止 sing-box和 argo 服务 systemctl stop "${server_name}" systemctl stop argo # 禁用 sing-box 服务 systemctl disable "${server_name}" systemctl disable argo # 重新加载 systemd systemctl daemon-reload || true fi # 删除配置文件和日志 rm -rf "${work_dir}" || true rm -rf "${log_dir}" || true rm -rf /etc/systemd/system/sing-box.service /etc/systemd/system/argo.service > /dev/null 2>&1 rm -rf /etc/nginx/conf.d/sing-box.conf > /dev/null 2>&1 # 卸载Nginx reading "\n是否卸载 Nginx?${green}(卸载请输入 ${yellow}y${re} ${green}回车将跳过卸载Nginx) (y/n): ${re}" choice case "${choice}" in y|Y) manage_packages uninstall nginx ;; *) yellow "取消卸载Nginx\n\n" ;; esac green "\nsing-box 卸载成功\n\n" && exit 0 ;; *) purple "已取消卸载操作\n\n" ;; esac } # 创建快捷指令 create_shortcut() { cat > "$work_dir/sb.sh" << EOF #!/usr/bin/env bash bash <(curl -Ls https://raw.githubusercontent.com/eooce/sing-box/main/sing-box.sh) \$1 EOF chmod +x "$work_dir/sb.sh" ln -sf "$work_dir/sb.sh" /usr/bin/sb if [ -s /usr/bin/sb ]; then green "\n快捷指令 sb 创建成功\n" else red "\n快捷指令创建失败\n" fi } # 适配alpine运行argo报错用户组和dns的问题 change_hosts() { sh -c 'echo "0 0" > /proc/sys/net/ipv4/ping_group_range' sed -i '1s/.*/127.0.0.1 localhost/' /etc/hosts sed -i '2s/.*/::1 localhost/' /etc/hosts } # 变更配置 change_config() { # 检查sing-box状态 local singbox_status=$(check_singbox 2>/dev/null) local singbox_installed=$? if [ $singbox_installed -eq 2 ]; then yellow "sing-box 尚未安装!" sleep 1 menu return fi clear echo "" green "=== 修改节点配置 ===\n" green "sing-box当前状态: $singbox_status\n" green "1. 修改端口" skyblue "------------" green "2. 修改UUID" skyblue "------------" green "3. 修改Reality伪装域名" skyblue "------------" green "4. 添加hysteria2端口跳跃" skyblue "------------" green "5. 删除hysteria2端口跳跃" skyblue "------------" green "6. 修改vmess-argo优选域名" skyblue "------------" purple "0. 返回主菜单" skyblue "------------" reading "请输入选择: " choice case "${choice}" in 1) echo "" green "1. 修改vless-reality端口" skyblue "------------" green "2. 修改hysteria2端口" skyblue "------------" green "3. 修改tuic端口" skyblue "------------" green "4. 修改vmess-argo端口" skyblue "------------" purple "0. 返回上一级菜单" skyblue "------------" reading "请输入选择: " choice case "${choice}" in 1) reading "\n请输入vless-reality端口 (回车跳过将使用随机端口): " new_port [ -z "$new_port" ] && new_port=$(shuf -i 2000-65000 -n 1) sed -i '/"type": "vless"/,/listen_port/ s/"listen_port": [0-9]\+/"listen_port": '"$new_port"'/' $config_dir restart_singbox allow_port $new_port/tcp > /dev/null 2>&1 sed -i 's/\(vless:\/\/[^@]*@[^:]*:\)[0-9]\{1,\}/\1'"$new_port"'/' $client_dir base64 -w0 /etc/sing-box/url.txt > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt green "\nvless-reality端口已修改成:${purple}$new_port${re} ${green}请更新订阅或手动更改vless-reality端口${re}\n" ;; 2) reading "\n请输入hysteria2端口 (回车跳过将使用随机端口): " new_port [ -z "$new_port" ] && new_port=$(shuf -i 2000-65000 -n 1) sed -i '/"type": "hysteria2"/,/listen_port/ s/"listen_port": [0-9]\+/"listen_port": '"$new_port"'/' $config_dir restart_singbox allow_port $new_port/udp > /dev/null 2>&1 sed -i 's/\(hysteria2:\/\/[^@]*@[^:]*:\)[0-9]\{1,\}/\1'"$new_port"'/' $client_dir base64 -w0 $client_dir > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt green "\nhysteria2端口已修改为:${purple}${new_port}${re} ${green}请更新订阅或手动更改hysteria2端口${re}\n" ;; 3) reading "\n请输入tuic端口 (回车跳过将使用随机端口): " new_port [ -z "$new_port" ] && new_port=$(shuf -i 2000-65000 -n 1) sed -i '/"type": "tuic"/,/listen_port/ s/"listen_port": [0-9]\+/"listen_port": '"$new_port"'/' $config_dir restart_singbox allow_port $new_port/udp > /dev/null 2>&1 sed -i 's/\(tuic:\/\/[^@]*@[^:]*:\)[0-9]\{1,\}/\1'"$new_port"'/' $client_dir base64 -w0 $client_dir > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt green "\ntuic端口已修改为:${purple}${new_port}${re} ${green}请更新订阅或手动更改tuic端口${re}\n" ;; 4) reading "\n请输入vmess-argo端口 (回车跳过将使用随机端口): " new_port [ -z "$new_port" ] && new_port=$(shuf -i 2000-65000 -n 1) sed -i '/"type": "vmess"/,/listen_port/ s/"listen_port": [0-9]\+/"listen_port": '"$new_port"'/' $config_dir allow_port $new_port/tcp > /dev/null 2>&1 if command_exists rc-service; then if grep -q "localhost:" /etc/init.d/argo; then sed -i 's/localhost:[0-9]\{1,\}/localhost:'"$new_port"'/' /etc/init.d/argo get_quick_tunnel change_argo_domain fi else if grep -q "localhost:" /etc/systemd/system/argo.service; then sed -i 's/localhost:[0-9]\{1,\}/localhost:'"$new_port"'/' /etc/systemd/system/argo.service get_quick_tunnel change_argo_domain fi fi if [ -f /etc/sing-box/tunnel.yml ]; then sed -i 's/localhost:[0-9]\{1,\}/localhost:'"$new_port"'/' /etc/sing-box/tunnel.yml restart_argo fi if ([ -f /etc/systemd/system/argo.service ] && grep -q -- "--token" /etc/systemd/system/argo.service) || \ ([ -f /etc/init.d/argo ] && grep -q -- "--token" /etc/init.d/argo); then yellow "请在cloudflared里也对应修改端口为:${purple}${new_port}${re}\n" fi restart_singbox green "\nvmess-argo端口已修改为:${purple}${new_port}${re}\n" ;; 0) change_config ;; *) red "无效的选项,请输入 1 到 4" ;; esac ;; 2) reading "\n请输入新的UUID: " new_uuid [ -z "$new_uuid" ] && new_uuid=$(cat /proc/sys/kernel/random/uuid) sed -i -E ' s/"uuid": "([a-f0-9-]+)"/"uuid": "'"$new_uuid"'"/g; s/"uuid": "([a-f0-9-]+)"$/\"uuid\": \"'$new_uuid'\"/g; s/"password": "([a-f0-9-]+)"/"password": "'"$new_uuid"'"/g ' $config_dir restart_singbox sed -i -E 's/(vless:\/\/|hysteria2:\/\/)[^@]*(@.*)/\1'"$new_uuid"'\2/' $client_dir sed -i "s/tuic:\/\/[0-9a-f\-]\{36\}/tuic:\/\/$new_uuid/" /etc/sing-box/url.txt isp=$(curl -s https://speed.cloudflare.com/meta | awk -F\" '{print $26"-"$18}' | sed -e 's/ /_/g') argodomain=$(grep -oE 'https://[[:alnum:]+\.-]+\.trycloudflare\.com' "${work_dir}/argo.log" | sed 's@https://@@') VMESS="{ \"v\": \"2\", \"ps\": \"${isp}\", \"add\": \"www.visa.com.tw\", \"port\": \"443\", \"id\": \"${new_uuid}\", \"aid\": \"0\", \"scy\": \"none\", \"net\": \"ws\", \"type\": \"none\", \"host\": \"${argodomain}\", \"path\": \"/vmess-argo?ed=2560\", \"tls\": \"tls\", \"sni\": \"${argodomain}\", \"alpn\": \"\", \"fp\": \"\", \"allowlnsecure\": \"flase\"}" encoded_vmess=$(echo "$VMESS" | base64 -w0) sed -i -E '/vmess:\/\//{s@vmess://.*@vmess://'"$encoded_vmess"'@}' $client_dir base64 -w0 $client_dir > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt green "\nUUID已修改为:${purple}${new_uuid}${re} ${green}请更新订阅或手动更改所有节点的UUID${re}\n" ;; 3) clear green "\n1. www.joom.com\n\n2. www.stengg.com\n\n3. www.wedgehr.com\n\n4. www.cerebrium.ai\n\n5. www.nazhumi.com\n" reading "\n请输入新的Reality伪装域名(可自定义输入,回车留空将使用默认1): " new_sni if [ -z "$new_sni" ]; then new_sni="www.joom.com" elif [[ "$new_sni" == "1" ]]; then new_sni="www.joom.com" elif [[ "$new_sni" == "2" ]]; then new_sni="www.stengg.com" elif [[ "$new_sni" == "3" ]]; then new_sni="www.wedgehr.com" elif [[ "$new_sni" == "4" ]]; then new_sni="www.cerebrium.ai" elif [[ "$new_sni" == "5" ]]; then new_sni="www.nazhumi.com" else new_sni="$new_sni" fi jq --arg new_sni "$new_sni" ' (.inbounds[] | select(.type == "vless") | .tls.server_name) = $new_sni | (.inbounds[] | select(.type == "vless") | .tls.reality.handshake.server) = $new_sni ' "$config_dir" > "$config_file.tmp" && mv "$config_file.tmp" "$config_dir" restart_singbox sed -i "s/\(vless:\/\/[^\?]*\?\([^\&]*\&\)*sni=\)[^&]*/\1$new_sni/" $client_dir base64 -w0 $client_dir > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt echo "" green "\nReality sni已修改为:${purple}${new_sni}${re} ${green}请更新订阅或手动更改reality节点的sni域名${re}\n" ;; 4) purple "端口跳跃需确保跳跃区间的端口没有被占用,nat鸡请注意可用端口范围,否则可能造成节点不通\n" reading "请输入跳跃起始端口 (回车跳过将使用随机端口): " min_port [ -z "$min_port" ] && min_port=$(shuf -i 50000-65000 -n 1) yellow "你的起始端口为:$min_port" reading "\n请输入跳跃结束端口 (需大于起始端口): " max_port [ -z "$max_port" ] && max_port=$(($min_port + 100)) yellow "你的结束端口为:$max_port\n" purple "正在安装依赖,并设置端口跳跃规则中,请稍等...\n" listen_port=$(sed -n '/"tag": "hysteria2"/,/}/s/.*"listen_port": \([0-9]*\).*/\1/p' $config_dir) iptables -t nat -A PREROUTING -p udp --dport $min_port:$max_port -j DNAT --to-destination :$listen_port > /dev/null command -v ip6tables &> /dev/null && ip6tables -t nat -A PREROUTING -p udp --dport $min_port:$max_port -j DNAT --to-destination :$listen_port > /dev/null if command_exists rc-service 2>/dev/null; then iptables-save > /etc/iptables/rules.v4 command -v ip6tables &> /dev/null && ip6tables-save > /etc/iptables/rules.v6 cat << 'EOF' > /etc/init.d/iptables #!/sbin/openrc-run depend() { need net } start() { [ -f /etc/iptables/rules.v4 ] && iptables-restore < /etc/iptables/rules.v4 command -v ip6tables &> /dev/null && [ -f /etc/iptables/rules.v6 ] && ip6tables-restore < /etc/iptables/rules.v6 } EOF chmod +x /etc/init.d/iptables && rc-update add iptables default && /etc/init.d/iptables start elif [ -f /etc/debian_version ]; then DEBIAN_FRONTEND=noninteractive apt install -y iptables-persistent > /dev/null 2>&1 && netfilter-persistent save > /dev/null 2>&1 systemctl enable netfilter-persistent > /dev/null 2>&1 && systemctl start netfilter-persistent > /dev/null 2>&1 elif [ -f /etc/redhat-release ]; then manage_packages install iptables-services > /dev/null 2>&1 && service iptables save > /dev/null 2>&1 systemctl enable iptables > /dev/null 2>&1 && systemctl start iptables > /dev/null 2>&1 command -v ip6tables &> /dev/null && service ip6tables save > /dev/null 2>&1 systemctl enable ip6tables > /dev/null 2>&1 && systemctl start ip6tables > /dev/null 2>&1 else red "未知系统,请自行将跳跃端口转发到主端口" && exit 1 fi restart_singbox ip=$(get_realip) uuid=$(sed -n 's/.*hysteria2:\/\/\([^@]*\)@.*/\1/p' $client_dir) line_number=$(grep -n 'hysteria2://' $client_dir | cut -d':' -f1) isp=$(curl -s --max-time 2 https://speed.cloudflare.com/meta | awk -F\" '{print $26"-"$18}' | sed -e 's/ /_/g' || echo "vps") sed -i.bak "/hysteria2:/d" $client_dir sed -i "${line_number}i hysteria2://$uuid@$ip:$listen_port?peer=www.bing.com&insecure=1&alpn=h3&obfs=none&mport=$listen_port,$min_port-$max_port#$isp" $client_dir base64 -w0 $client_dir > /etc/sing-box/sub.txt while IFS= read -r line; do yellow "$line"; done < ${work_dir}/url.txt green "\nhysteria2端口跳跃已开启,跳跃端口为:${purple}$min_port-$max_port${re} ${green}请更新订阅或手动复制以上hysteria2节点${re}\n" ;; 5) iptables -t nat -F PREROUTING > /dev/null 2>&1 command -v ip6tables &> /dev/null && ip6tables -t nat -F PREROUTING > /dev/null 2>&1 if command_exists rc-service 2>/dev/null; then rc-update del iptables default && rm -rf /etc/init.d/iptables elif [ -f /etc/redhat-release ]; then netfilter-persistent save > /dev/null 2>&1 elif [ -f /etc/redhat-release ]; then service iptables save > /dev/null 2>&1 command -v ip6tables &> /dev/null && service ip6tables save > /dev/null 2>&1 else manage_packages uninstall iptables ip6tables iptables-persistent iptables-service > /dev/null 2>&1 fi sed -i '/hysteria2/s/&mport=[^#&]*//g' /etc/sing-box/url.txt base64 -w0 $client_dir > /etc/sing-box/sub.txt green "\n端口跳跃已删除\n" ;; 6) change_cfip ;; 0) menu ;; *) read "无效的选项!" ;; esac } disable_open_sub() { local singbox_status=$(check_singbox 2>/dev/null) local singbox_installed=$? if [ $singbox_installed -eq 2 ]; then yellow "sing-box 尚未安装!" sleep 1 menu return fi clear echo "" green "=== 管理节点订阅 ===\n" skyblue "------------" green "1. 关闭节点订阅" skyblue "------------" green "2. 开启节点订阅" skyblue "------------" green "3. 更换订阅端口" skyblue "------------" purple "0. 返回主菜单" skyblue "------------" reading "请输入选择: " choice case "${choice}" in 1) if command -v nginx &>/dev/null; then if command_exists rc-service 2>/dev/null; then rc-service nginx status | grep -q "started" && rc-service nginx stop || red "nginx not running" else [ "$(systemctl is-active nginx)" = "active" ] && systemctl stop nginx || red "ngixn not running" fi else yellow "Nginx is not installed" fi green "\n已关闭节点订阅\n" ;; 2) green "\n已开启节点订阅\n" server_ip=$(get_realip) password=$(tr -dc A-Za-z < /dev/urandom | head -c 32) sed -i "s|\(location = /\)[^ ]*|\1$password|" /etc/nginx/conf.d/sing-box.conf sub_port=$(port=$(grep -E 'listen [0-9]+;' "/etc/nginx/conf.d/sing-box.conf" | awk '{print $2}' | sed 's/;//'); if [ "$port" -eq 80 ]; then echo ""; else echo "$port"; fi) start_nginx (port=$(grep -E 'listen [0-9]+;' "/etc/nginx/conf.d/sing-box.conf" | awk '{print $2}' | sed 's/;//'); if [ "$port" -eq 80 ]; then echo ""; else green "订阅端口:$port"; fi); link=$(if [ -z "$sub_port" ]; then echo "http://$server_ip/$password"; else echo "http://$server_ip:$sub_port/$password"; fi); green "\n新的节点订阅链接:$link\n" ;; 3) reading "请输入新的订阅端口(1-65535):" sub_port [ -z "$sub_port" ] && sub_port=$(shuf -i 2000-65000 -n 1) # 检查端口是否被占用 until [[ -z $(lsof -iTCP:"$sub_port" -sTCP:LISTEN -t) ]]; do if [[ -n $(lsof -iTCP:"$sub_port" -sTCP:LISTEN -t) ]]; then echo -e "${red}端口 $sub_port 已经被其他程序占用,请更换端口重试${re}" reading "请输入新的订阅端口(1-65535):" sub_port [[ -z $sub_port ]] && sub_port=$(shuf -i 2000-65000 -n 1) fi done # 备份当前配置 if [ -f "/etc/nginx/conf.d/sing-box.conf" ]; then cp "/etc/nginx/conf.d/sing-box.conf" "/etc/nginx/conf.d/sing-box.conf.bak.$(date +%Y%m%d)" fi # 更新端口配置 sed -i 's/listen [0-9]\+;/listen '$sub_port';/g' "/etc/nginx/conf.d/sing-box.conf" sed -i 's/listen \[::\]:[0-9]\+;/listen [::]:'$sub_port';/g' "/etc/nginx/conf.d/sing-box.conf" path=$(sed -n 's|.*location = /\([^ ]*\).*|\1|p' "/etc/nginx/conf.d/sing-box.conf") server_ip=$(get_realip) # 放行新端口 allow_port $sub_port/tcp > /dev/null 2>&1 # 测试nginx配置 if nginx -t > /dev/null 2>&1; then # 尝试重新加载配置 if nginx -s reload > /dev/null 2>&1; then green "nginx配置已重新加载,端口更换成功" else yellow "配置重新加载失败,尝试重启nginx服务..." restart_nginx fi green "\n订阅端口更换成功\n" green "新的订阅链接为:http://$server_ip:$sub_port/$path\n" else red "nginx配置测试失败,正在恢复原有配置..." if [ -f "/etc/nginx/conf.d/sing-box.conf.bak."* ]; then latest_backup=$(ls -t /etc/nginx/conf.d/sing-box.conf.bak.* | head -1) cp "$latest_backup" "/etc/nginx/conf.d/sing-box.conf" yellow "已恢复原有nginx配置" fi return 1 fi ;; 0) menu ;; *) red "无效的选项!" ;; esac } # singbox 管理 manage_singbox() { # 检查sing-box状态 local singbox_status=$(check_singbox 2>/dev/null) local singbox_installed=$? clear echo "" green "=== sing-box 管理 ===\n" green "sing-box当前状态: $singbox_status\n" green "1. 启动sing-box服务" skyblue "-------------------" green "2. 停止sing-box服务" skyblue "-------------------" green "3. 重启sing-box服务" skyblue "-------------------" purple "0. 返回主菜单" skyblue "------------" reading "\n请输入选择: " choice case "${choice}" in 1) start_singbox ;; 2) stop_singbox ;; 3) restart_singbox ;; 0) menu ;; *) red "无效的选项!" && sleep 1 && manage_singbox;; esac } # Argo 管理 manage_argo() { # 检查Argo状态 local argo_status=$(check_argo 2>/dev/null) local argo_installed=$? clear echo "" green "=== Argo 隧道管理 ===\n" green "Argo当前状态: $argo_status\n" green "1. 启动Argo服务" skyblue "------------" green "2. 停止Argo服务" skyblue "------------" green "3. 重启Argo服务" skyblue "------------" green "4. 添加Argo固定隧道" skyblue "----------------" green "5. 切换回Argo临时隧道" skyblue "------------------" green "6. 重新获取Argo临时域名" skyblue "-------------------" purple "0. 返回主菜单" skyblue "-----------" reading "\n请输入选择: " choice case "${choice}" in 1) start_argo ;; 2) stop_argo ;; 3) clear if command_exists rc-service 2>/dev/null; then grep -Fq -- '--url http://localhost' /etc/init.d/argo && get_quick_tunnel && change_argo_domain || { green "\n当前使用固定隧道,无需获取临时域名"; sleep 2; menu; } else grep -q 'ExecStart=.*--url http://localhost' /etc/systemd/system/argo.service && get_quick_tunnel && change_argo_domain || { green "\n当前使用固定隧道,无需获取临时域名"; sleep 2; menu; } fi ;; 4) clear yellow "\n固定隧道可为json或token,固定隧道端口为8001,自行在cf后台设置\n\njson在f佬维护的站点里获取,获取地址:${purple}https://fscarmen.cloudflare.now.cc${re}\n" reading "\n请输入你的argo域名: " argo_domain ArgoDomain=$argo_domain reading "\n请输入你的argo密钥(token或json): " argo_auth if [[ $argo_auth =~ TunnelSecret ]]; then echo $argo_auth > ${work_dir}/tunnel.json cat > ${work_dir}/tunnel.yml << EOF tunnel: $(cut -d\" -f12 <<< "$argo_auth") credentials-file: ${work_dir}/tunnel.json protocol: http2 ingress: - hostname: $ArgoDomain service: http://localhost:8001 originRequest: noTLSVerify: true - service: http_status:404 EOF if command_exists rc-service 2>/dev/null; then sed -i '/^command_args=/c\command_args="-c '\''/etc/sing-box/argo tunnel --edge-ip-version auto --config /etc/sing-box/tunnel.yml run 2>&1'\''"' /etc/init.d/argo else sed -i '/^ExecStart=/c ExecStart=/bin/sh -c "/etc/sing-box/argo tunnel --edge-ip-version auto --config /etc/sing-box/tunnel.yml run 2>&1"' /etc/systemd/system/argo.service fi restart_argo sleep 1 change_argo_domain elif [[ $argo_auth =~ ^[A-Z0-9a-z=]{120,250}$ ]]; then if command_exists rc-service 2>/dev/null; then sed -i "/^command_args=/c\command_args=\"-c '/etc/sing-box/argo tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token $argo_auth 2>&1'\"" /etc/init.d/argo else sed -i '/^ExecStart=/c ExecStart=/bin/sh -c "/etc/sing-box/argo tunnel --edge-ip-version auto --no-autoupdate --protocol http2 run --token '$argo_auth' 2>&1"' /etc/systemd/system/argo.service fi restart_argo sleep 1 change_argo_domain else yellow "你输入的argo域名或token不匹配,请重新输入" manage_argo fi ;; 5) clear if command_exists rc-service 2>/dev/null; then alpine_openrc_services else main_systemd_services fi get_quick_tunnel change_argo_domain ;; 6) if command_exists rc-service 2>/dev/null; then if grep -Fq -- '--url http://localhost' "/etc/init.d/argo"; then get_quick_tunnel change_argo_domain else yellow "当前使用固定隧道,无法获取临时隧道" sleep 2 menu fi else if grep -q 'ExecStart=.*--url http://localhost' "/etc/systemd/system/argo.service"; then get_quick_tunnel change_argo_domain else yellow "当前使用固定隧道,无法获取临时隧道" sleep 2 menu fi fi ;; 0) menu ;; *) red "无效的选项!" ;; esac } # 获取argo临时隧道 get_quick_tunnel() { restart_argo yellow "获取临时argo域名中,请稍等...\n" sleep 3 if [ -f /etc/sing-box/argo.log ]; then for i in {1..5}; do purple "第 $i 次尝试获取ArgoDoamin中..." get_argodomain=$(sed -n 's|.*https://\([^/]*trycloudflare\.com\).*|\1|p' "/etc/sing-box/argo.log") [ -n "$get_argodomain" ] && break sleep 2 done else restart_argo sleep 6 get_argodomain=$(sed -n 's|.*https://\([^/]*trycloudflare\.com\).*|\1|p' "/etc/sing-box/argo.log") fi green "ArgoDomain:${purple}$get_argodomain${re}\n" ArgoDomain=$get_argodomain } # 更新Argo域名到订阅 change_argo_domain() { content=$(cat "$client_dir") vmess_url=$(grep -o 'vmess://[^ ]*' "$client_dir") vmess_prefix="vmess://" encoded_vmess="${vmess_url#"$vmess_prefix"}" decoded_vmess=$(echo "$encoded_vmess" | base64 --decode) updated_vmess=$(echo "$decoded_vmess" | jq --arg new_domain "$ArgoDomain" '.host = $new_domain | .sni = $new_domain') encoded_updated_vmess=$(echo "$updated_vmess" | base64 | tr -d '\n') new_vmess_url="${vmess_prefix}${encoded_updated_vmess}" new_content=$(echo "$content" | sed "s|$vmess_url|$new_vmess_url|") echo "$new_content" > "$client_dir" base64 -w0 ${work_dir}/url.txt > ${work_dir}/sub.txt green "vmess节点已更新,更新订阅或手动复制以下vmess-argo节点\n" purple "$new_vmess_url\n" } # 查看节点信息和订阅链接 check_nodes() { while IFS= read -r line; do purple "${purple}$line"; done < ${work_dir}/url.txt server_ip=$(get_realip) lujing=$(sed -n 's|.*location = /\([^ ]*\).*|\1|p' "/etc/nginx/conf.d/sing-box.conf") sub_port=$(sed -n 's/^\s*listen \([0-9]\+\);/\1/p' "/etc/nginx/conf.d/sing-box.conf") base64_url="http://${server_ip}:${sub_port}/${lujing}" green "\n\nSurge订阅链接: ${purple}https://sublink.eooce.com/surge?config=${base64_url}${re}\n" green "sing-box订阅链接: ${purple}https://sublink.eooce.com/singbox?config=${base64_url}${purple}\n" green "Mihomo/Clash系列订阅链接: ${purple}https://sublink.eooce.com/clash?config=${base64_url}${re}\n" green "V2rayN,Shadowrocket,Nekobox,Loon,Karing,Sterisand订阅链接: ${purple}${base64_url}${re}\n" } change_cfip() { clear yellow "修改vmess-argo优选域名\n" green "1: cf.090227.xyz 2: cf.877774.xyz 3: cf.877771.xyz 4: cdns.doon.eu.org 5: cf.zhetengsha.eu.org 6: time.is\n" reading "请输入你的优选域名或优选IP\n(请输入1至6选项,可输入域名:端口 或 IP:端口,直接回车默认使用1): " cfip_input if [ -z "$cfip_input" ]; then cfip="cf.090227.xyz" cfport="443" else case "$cfip_input" in "1") cfip="cf.090227.xyz" cfport="443" ;; "2") cfip="cf.877774.xyz" cfport="443" ;; "3") cfip="cf.877771.xyz" cfport="443" ;; "4") cfip="cdns.doon.eu.org" cfport="443" ;; "5") cfip="cf.zhetengsha.eu.org" cfport="443" ;; "6") cfip="time.is" cfport="443" ;; *) if [[ "$cfip_input" =~ : ]]; then cfip=$(echo "$cfip_input" | cut -d':' -f1) cfport=$(echo "$cfip_input" | cut -d':' -f2) else cfip="$cfip_input" cfport="443" fi ;; esac fi content=$(cat "$client_dir") vmess_url=$(grep -o 'vmess://[^ ]*' "$client_dir") encoded_part="${vmess_url#vmess://}" decoded_json=$(echo "$encoded_part" | base64 --decode 2>/dev/null) updated_json=$(echo "$decoded_json" | jq --arg cfip "$cfip" --argjson cfport "$cfport" \ '.add = $cfip | .port = $cfport') new_encoded_part=$(echo "$updated_json" | base64 -w0) new_vmess_url="vmess://$new_encoded_part" new_content=$(echo "$content" | sed "s|$vmess_url|$new_vmess_url|") echo "$new_content" > "$client_dir" base64 -w0 "${work_dir}/url.txt" > "${work_dir}/sub.txt" green "\nvmess节点优选域名已更新为:${purple}${cfip}:${cfport},${green}更新订阅或手动复制以下vmess-argo节点${re}\n" purple "$new_vmess_url\n" } # 主菜单 menu() { singbox_status=$(check_singbox 2>/dev/null) nginx_status=$(check_nginx 2>/dev/null) argo_status=$(check_argo 2>/dev/null) clear echo "" green "Telegram群组: ${purple}https://t.me/eooceu${re}" green "YouTube频道: ${purple}https://youtube.com/@eooce${re}" green "Github地址: ${purple}https://github.com/eooce/sing-box${re}\n" purple "=== 老王sing-box四合一安装脚本 ===\n" purple "---Argo 状态: ${argo_status}" purple "--Nginx 状态: ${nginx_status}" purple "singbox 状态: ${singbox_status}\n" green "1. 安装sing-box" red "2. 卸载sing-box" echo "===============" green "3. sing-box管理" green "4. Argo隧道管理" echo "===============" green "5. 查看节点信息" green "6. 修改节点配置" green "7. 管理节点订阅" echo "===============" purple "8. ssh综合工具箱" echo "===============" red "0. 退出脚本" echo "===========" reading "请输入选择(0-9): " choice echo "" } # 捕获 Ctrl+C 退出信号 trap 'red "已取消操作"; exit' INT # 主循环 while true; do menu case "${choice}" in 1) check_singbox &>/dev/null; check_singbox=$? if [ ${check_singbox} -eq 0 ]; then yellow "sing-box 已经安装!\n" else manage_packages install nginx jq tar openssl lsof coreutils install_singbox if command_exists systemctl; then main_systemd_services elif command_exists rc-update; then alpine_openrc_services change_hosts rc-service sing-box restart rc-service argo restart else echo "Unsupported init system" exit 1 fi sleep 5 get_info add_nginx_conf create_shortcut fi ;; 2) uninstall_singbox ;; 3) manage_singbox ;; 4) manage_argo ;; 5) check_nodes ;; 6) change_config ;; 7) disable_open_sub ;; 8) clear bash <(curl -Ls ssh_tool.eooce.com) ;; 0) exit 0 ;; *) red "无效的选项,请输入 0 到 8" ;; esac read -n 1 -s -r -p $'\033[1;91m按任意键返回...\033[0m' done