From e4e5b6a46ca1f9cc46d490b937cb970367936d26 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sun, 5 Jul 2026 07:51:11 +0000 Subject: [PATCH] Add Snell script with Sub-Store JSON output --- scripts/snell-README.md | 23 + scripts/snell.sh | 2601 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 2624 insertions(+) create mode 100644 scripts/snell-README.md create mode 100644 scripts/snell.sh diff --git a/scripts/snell-README.md b/scripts/snell-README.md new file mode 100644 index 0000000..97c3241 --- /dev/null +++ b/scripts/snell-README.md @@ -0,0 +1,23 @@ +# Snell.sh Mango fork + +Based on upstream `https://raw.githubusercontent.com/xOS/Snell/master/Snell.sh` fetched 2026-07-05. + +Changes: +- `viewConfig` still prints Surge config. +- Adds Mihomo/Nikki JSON node output for Snell v1-v5, compatible with Sub-Store JSON input/output. +- For Snell v6, prints a warning instead of invalid Mihomo config because current MetaCubeX docs only list Snell v1/2/3/4/5 support. +- `updateShell` can be pointed at your own raw script URL using environment variables: + - `SNELL_SCRIPT_URL=https://gitea.example.com/user/repo/raw/branch/main/Snell.sh` + - `SNELL_SCRIPT_CN_URL=https://gitea.example.com/user/repo/raw/branch/main/Snell.sh` + +Install example after publishing to Gitea: + +```bash +wget -O snell.sh --no-check-certificate https://YOUR-GITEA/user/repo/raw/branch/main/Snell.sh && chmod +x snell.sh && ./snell.sh +``` + +Example Mihomo/Sub-Store JSON node: + +```json +{"name":"vps","server":"1.2.3.4","port":2345,"psk":"secret","version":5,"reuse":true,"tfo":true,"type":"snell"} +``` diff --git a/scripts/snell.sh b/scripts/snell.sh new file mode 100644 index 0000000..c91d19f --- /dev/null +++ b/scripts/snell.sh @@ -0,0 +1,2601 @@ +#!/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: Snell Server 管理脚本 +# Author: 翠花 +# WebSite: https://aapls.com +#================================================= + +sh_ver="1.9.6" +snell_v2_version="2.0.6" +snell_v3_version="3.0.1" +snell_v4_version="4.1.1" +snell_v5_version="5.0.1" +snell_v6_version="6.0.0b4" +script_dir=$(cd "$(dirname "$0")"; pwd) +script_path=$(echo -e "${script_dir}"|awk -F "$0" '{print $1}') +snell_dir="/etc/snell/" +snell_bin="/usr/local/bin/snell-server" +snell_conf="/etc/snell/config.conf" +snell_version_file="/etc/snell/ver.txt" +sysctl_conf="/etc/sysctl.d/99-snell.conf" + +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}" + +# 检查是否为 Root 用户 +checkRoot(){ + [[ $EUID != 0 ]] && echo -e "${Error} 当前非ROOT账号(或没有ROOT权限),无法继续操作,请更换ROOT账号或使用 ${Green_background_prefix}sudo su${Font_color_suffix} 命令获取临时ROOT权限(执行后可能会提示输入当前账号的密码)。" && exit 1 +} + +# 检查系统类型 +checkSys(){ + 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 +} + +# 检查依赖 +checkDependencies(){ + local deps=("wget" "unzip" "ss") + for cmd in "${deps[@]}"; do + if ! command -v "$cmd" &> /dev/null; then + echo -e "${Error} 缺少依赖: $cmd,正在尝试安装..." + if [[ -f /etc/debian_version ]]; then + apt-get update && apt-get install -y "$cmd" + elif [[ -f /etc/redhat-release ]]; then + yum install -y "$cmd" + else + echo -e "${Error} 不支持的系统,无法自动安装 $cmd" + exit 1 + fi + fi + done + echo -e "${Info} 依赖检查完成" +} + +# 安装依赖 +installDependencies(){ + if [[ ${release} == "centos" ]]; then + yum update + yum install gzip wget curl unzip -y + else + apt-get update + apt-get install gzip wget curl unzip -y + fi + sysctl -w net.core.rmem_max=26214400 + sysctl -w net.core.rmem_default=26214400 + \cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + echo -e "${Info} 依赖安装完成" +} + +# 检查系统架构 +sysArch() { + uname=$(uname -m) + if [[ "$uname" == "i686" ]] || [[ "$uname" == "i386" ]]; then + arch="i386" + elif [[ "$uname" == *"armv7"* ]] || [[ "$uname" == "armv6l" ]]; then + arch="armv7l" + elif [[ "$uname" == *"armv8"* ]] || [[ "$uname" == "aarch64" ]]; then + arch="aarch64" + else + arch="amd64" + fi +} + +# 开启 TCP Fast Open +enableTCPFastOpen() { + kernel=$(uname -r | awk -F . '{print $1}') + if [ "$kernel" -ge 3 ]; then + echo 3 >/proc/sys/net/ipv4/tcp_fastopen + # 创建或覆盖 Snell 专用的 sysctl 配置文件 + cat > "$sysctl_conf" << EOF +# Snell Server 网络优化配置 +# 由 Snell 管理脚本自动生成 + +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 + +# BBR 拥塞控制 +net.core.default_qdisc = fq +net.ipv4.tcp_congestion_control = bbr +EOF + # 应用配置 + sysctl --system >/dev/null 2>&1 + echo -e "${Info} TCP Fast Open 和网络优化配置已启用!" + else + echo -e "${Error} 系统内核版本过低,无法支持 TCP Fast Open!" + fi +} + +# 检查 Snell 是否安装 +checkInstalledStatus(){ + [[ ! -e ${snell_bin} ]] && echo -e "${Error} Snell Server 没有安装,请检查!" && exit 1 +} + +# 检查 Snell 运行状态 +checkStatus(){ + if systemctl is-active snell-server.service &> /dev/null; then + status="running" + else + status="stopped" + fi +} + +# 版本号比较函数(优先正式版) +compareVersions(){ + local version1="$1" + local version2="$2" + + # 移除版本号前缀 v + version1=$(echo "$version1" | sed 's/^v//') + version2=$(echo "$version2" | sed 's/^v//') + + # 如果版本号完全相同 + if [[ "$version1" == "$version2" ]]; then + return 1 # 相等 + fi + + # 提取基础版本号(去除测试版后缀) + local base_version1=$(echo "$version1" | sed 's/[a-z].*//') + local base_version2=$(echo "$version2" | sed 's/[a-z].*//') + + # 检查是否为测试版 + local is_beta1=false + local is_beta2=false + [[ "$version1" =~ [a-z] ]] && is_beta1=true + [[ "$version2" =~ [a-z] ]] && is_beta2=true + + # 如果基础版本号相同 + if [[ "$base_version1" == "$base_version2" ]]; then + # 优先选择正式版 + if [[ "$is_beta1" == true && "$is_beta2" == false ]]; then + return 2 # version1 < version2(正式版优先) + elif [[ "$is_beta1" == false && "$is_beta2" == true ]]; then + return 0 # version1 > version2(正式版优先) + fi + # 如果都是测试版或都是正式版,使用字母序比较 + if [[ "$version1" < "$version2" ]]; then + return 2 + else + return 0 + fi + fi + + # 基础版本号不同时,使用 sort -V 进行版本号比较 + if printf '%s\n' "$base_version1" "$base_version2" | sort -V | head -1 | grep -q "^$base_version1$"; then + return 2 # version1 < version2 + else + return 0 # version1 > version2 + fi +} + +# 验证版本 URL 是否有效 +validateVersionUrl(){ + local version="$1" + getSnellDownloadUrl "$version" + + # 使用 HEAD 请求检查 URL 是否有效 + if curl -I -s --max-time 10 "$snell_url" | head -1 | grep -q "200 OK"; then + return 0 # URL 有效 + else + return 1 # URL 无效 + fi +} + +# 检查版本更新 +checkVersionUpdate(){ + local show_info=${1:-false} # 是否显示详细信息,默认为静默 + update_available=false + current_installed_version="" + latest_available_version="" + best_version="" + + if [[ -e ${snell_bin} && -e ${snell_conf} ]]; then + current_ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') + + # 除 Snell v6 版外的旧版本已停更,不再检查在线更新 + if [[ "$current_ver" != "6" ]]; then + update_available=false + return 0 + fi + + if [[ -e ${snell_version_file} ]]; then + installed_version=$(cat ${snell_version_file} | sed 's/^v//') + + # 尝试从二进制程序获取真实版本 (容错处理) + if command -v timeout >/dev/null 2>&1 && [[ -x ${snell_bin} ]]; then + bin_version=$(timeout 1 ${snell_bin} --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9]*' | head -1) + [[ -z "$bin_version" ]] && bin_version=$(timeout 1 ${snell_bin} -v 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9]*' | head -1) + [[ -z "$bin_version" ]] && bin_version=$(timeout 1 ${snell_bin} --help 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9]*' | head -1) + + if [[ -n "$bin_version" && "$bin_version" != "$installed_version" ]]; then + installed_version="$bin_version" + echo "v${installed_version}" > "${snell_version_file}" # 修复不同步的问题 + fi + fi + + current_installed_version="$installed_version" + + # 根据当前版本确定对应的脚本版本和网页版本 + case "$current_ver" in + "4") + script_version=${snell_v4_version} + web_version=$(getLatestVersionFromWeb "v4") + ;; + "5") + script_version=${snell_v5_version} + web_version=$(getLatestVersionFromWeb "v5") + ;; + "6") + script_version=${snell_v6_version} + web_version=$(getLatestVersionFromWeb "v6") + ;; + *) + script_version="" + web_version="" + ;; + esac + + # 优先使用脚本内置版本,除非网页版本更新 + best_version="$installed_version" + version_source="已安装" + + # 首先比较脚本内置版本 + if [[ -n "$script_version" ]]; then + compareVersions "$best_version" "$script_version" + case $? in + 2) # best_version < script_version + # 验证脚本内置版本的 URL 是否有效 + if validateVersionUrl "$script_version"; then + best_version="$script_version" + version_source="脚本内置" + else + [[ "$show_info" == true ]] && echo -e "${Tip} 脚本内置版本 v${script_version} 的下载链接无效,跳过" + fi + ;; + esac + fi + + # 然后比较网页版本,只有当网页版本比当前最佳版本更新时才采用 + if [[ -n "$web_version" ]]; then + compareVersions "$best_version" "$web_version" + case $? in + 2) # best_version < web_version + # 验证网页版本的 URL 是否有效 + if validateVersionUrl "$web_version"; then + best_version="$web_version" + version_source="官方网页" + else + [[ "$show_info" == true ]] && echo -e "${Tip} 网页版本 Snell v${web_version} 的下载链接无效,使用脚本内置版本" + fi + ;; + 1|0) # best_version >= web_version + # 脚本内置版本优先,无需显示 + ;; + esac + fi + + latest_available_version="$best_version" + + # 如果最佳版本与当前安装版本不同,则有更新可用 + compareVersions "$installed_version" "$best_version" + if [[ $? -eq 2 ]]; then + update_available=true + if [[ "$show_info" == true ]]; then + echo -e "${Info} 发现更新:当前版本 Snell v${installed_version} -> 最新版本 Snell v${best_version}" + echo -e "${Info} 更新版本来源:${version_source}" + fi + fi + fi + fi +} + + +# 获取 Snell 下载链接 +getSnellDownloadUrl(){ + sysArch + local version=$1 + snell_url="https://dl.nssurge.com/snell/snell-server-v${version}-linux-${arch}.zip" +} + + + +# 下载并安装 Snell v2 +downloadSnellV2() { + downloadSnellFromGitHub "${snell_v2_version}" "Snell v2 GitHub 备份源版" || downloadSnellFromGitee "${snell_v2_version}" "Snell v2 Gitee 备份源版" +} + +# 下载并安装 Snell v3 +downloadSnellV3() { + downloadSnellFromGitHub "${snell_v3_version}" "Snell v3 GitHub 备份源版" || downloadSnellFromGitee "${snell_v3_version}" "Snell v3 Gitee 备份源版" +} + +# 通用下载并安装 Snell 函数(第三方备份源) +downloadSnellFromBackup(){ + local version=$1 + local version_type=$2 + local backup_url=$3 + + echo -e "${Info} 试图请求 ${Yellow_font_prefix}${version_type}${Font_color_suffix} Snell Server ……" + + wget --no-check-certificate -N "${backup_url}" + if [[ ! -e "snell-server-v${version}-linux-${arch}.zip" ]]; then + echo -e "${Error} Snell Server ${Yellow_font_prefix}${version_type}${Font_color_suffix} 下载失败!" + return 1 + fi + + unzip -o "snell-server-v${version}-linux-${arch}.zip" + if [[ ! -e "snell-server" ]]; then + echo -e "${Error} Snell Server ${Yellow_font_prefix}${version_type}${Font_color_suffix} 解压失败!" + return 1 + fi + + rm -rf "snell-server-v${version}-linux-${arch}.zip" + chmod +x snell-server + mv -f snell-server "${snell_bin}" + echo "v${version}" > "${snell_version_file}" + echo -e "${Info} Snell Server 主程序下载安装完毕!" + return 0 +} + +downloadSnellFromGitHub(){ + local version=$1 + local version_type=$2 + local backup_url="https://raw.githubusercontent.com/xOS/Others/master/snell/v${version}/snell-server-v${version}-linux-${arch}.zip" + downloadSnellFromBackup "$version" "$version_type" "$backup_url" +} + +downloadSnellFromGitee(){ + local version=$1 + local version_type=$2 + local backup_url="https://gitee.com/ten/Others/raw/master/snell/v${version}/snell-server-v${version}-linux-${arch}.zip" + downloadSnellFromBackup "$version" "$version_type" "$backup_url" +} + +# 下载并安装 Snell v4 +downloadSnellV4(){ + downloadSnell "${snell_v4_version}" "Snell v4 官网源版" || downloadSnellFromGitHub "${snell_v4_version}" "Snell v4 GitHub 备份源版" || downloadSnellFromGitee "${snell_v4_version}" "Snell v4 Gitee 备份源版" +} + +# 下载并安装 Snell v5 +downloadSnellV5(){ + downloadSnell "${snell_v5_version}" "Snell v5 官网源版" || downloadSnellFromGitHub "${snell_v5_version}" "Snell v5 GitHub 备份源版" || downloadSnellFromGitee "${snell_v5_version}" "Snell v5 Gitee 备份源版" +} + +# 通用下载并安装 Snell 函数(带回退机制) +downloadSnell(){ + local version=$1 + local version_type=$2 + local allow_fallback=${3:-false} + local fallback_version=$4 + + echo -e "${Info} 试图请求 ${Yellow_font_prefix}${version_type}${Font_color_suffix} Snell Server ……" + getSnellDownloadUrl "${version}" + + # 首先检查 URL 是否有效 + if ! curl -I -s --max-time 10 "$snell_url" | head -1 | grep -q "200 OK"; then + echo -e "${Error} Snell Server ${Yellow_font_prefix}${version_type}${Font_color_suffix} 下载链接无效 (404)!" + + # 如果允许回退且提供了回退版本 + if [[ "$allow_fallback" == true && -n "$fallback_version" ]]; then + echo -e "${Info} 尝试回退到已安装版本 v${fallback_version}..." + getSnellDownloadUrl "${fallback_version}" + if curl -I -s --max-time 10 "$snell_url" | head -1 | grep -q "200 OK"; then + version="$fallback_version" + echo -e "${Info} 回退成功,使用版本 v${version}" + else + echo -e "${Error} 回退版本也无法下载!" + return 1 + fi + else + return 1 + fi + fi + + wget --no-check-certificate -N "${snell_url}" + if [[ ! -e "snell-server-v${version}-linux-${arch}.zip" ]]; then + echo -e "${Error} Snell Server ${Yellow_font_prefix}${version_type}${Font_color_suffix} 下载失败!" + return 1 && exit 1 + else + unzip -o "snell-server-v${version}-linux-${arch}.zip" + fi + if [[ ! -e "snell-server" ]]; then + echo -e "${Error} Snell Server ${Yellow_font_prefix}${version_type}${Font_color_suffix} 解压失败!" + return 1 && exit 1 + else + rm -rf "snell-server-v${version}-linux-${arch}.zip" + chmod +x snell-server + mv -f snell-server "${snell_bin}" + echo "v${version}" > ${snell_version_file} + echo -e "${Info} Snell Server 主程序下载安装完毕!" + return 0 + fi +} + +# 安装 Snell +installSnell() { + if [[ ! -e "${snell_dir}" ]]; then + mkdir "${snell_dir}" + else + [[ -e "${snell_bin}" ]] && rm -rf "${snell_bin}" + fi + echo -e "选择安装版本${Yellow_font_prefix}[2-6]${Font_color_suffix} +================================== +${Green_font_prefix} 2.${Font_color_suffix} v2 ${Green_font_prefix} 3.${Font_color_suffix} v3 ${Green_font_prefix} 4.${Font_color_suffix} v4 ${Green_font_prefix} 5.${Font_color_suffix} v5 ${Green_font_prefix} 6.${Font_color_suffix} v6 +==================================" + read -e -p "(默认:4.v4):" ver + [[ -z "${ver}" ]] && ver="4" + if [[ ${ver} == "2" ]]; then + installSnellV2 + elif [[ ${ver} == "3" ]]; then + installSnellV3 + elif [[ ${ver} == "4" ]]; then + installSnellV4 + elif [[ ${ver} == "5" ]]; then + installSnellV5 + elif [[ ${ver} == "6" ]]; then + installSnellV6 + else + installSnellV4 + fi +} + +# 配置服务 +setupService(){ + echo ' +[Unit] +Description=Snell Service +After=network.target +[Service] +LimitNOFILE=32767 +Type=simple +User=root +Restart=on-failure +RestartSec=5s +ExecStart=/usr/local/bin/snell-server -c /etc/snell/config.conf +[Install] +WantedBy=multi-user.target' > /etc/systemd/system/snell-server.service + systemctl daemon-reload + systemctl enable snell-server + echo -e "${Info} Snell Server 服务配置完成!" +} + + +# 针对 Snell v6 检查密钥长度 +checkPskForV6(){ + if [[ ${#psk} -lt 16 ]] || [[ ${#psk} -gt 255 ]]; then + echo -e "${Error} 检测到当前密钥 (${psk}) 长度不符合 16-255 位要求,Snell v6 要求升级密钥!" + echo -e "请选择处理方式:" + echo -e " 1. 自动生成安全的随机长密钥(推荐)" + echo -e " 2. 手动输入新的长密钥" + read -e -p "(默认: 1):" psk_choice + [[ -z "${psk_choice}" ]] && psk_choice="1" + + if [[ "${psk_choice}" == "2" ]]; then + while true; do + read -e -p "请输入新的密钥(16-255位): " new_psk + if [[ ${#new_psk} -ge 16 ]] && [[ ${#new_psk} -le 255 ]]; then + psk=$new_psk + break + else + echo -e "${Error} 密钥长度必须在 16 到 255 位之间!" + fi + done + else + psk=$(tr -dc A-Za-z0-9 0 && $0 !~ /^[[:space:]]*$/) { + print $0 + } + } + }' "${snell_conf}") + + if [[ -n "${custom_configs}" ]]; then + config_content+=$'\n\n# Custom Configs\n'"${custom_configs}" + fi + fi + + echo "${config_content}" > "${snell_conf}" +} + + + + +# 读取配置文件 +readConfig(){ + [[ ! -e ${snell_conf} ]] && echo -e "${Error} Snell Server 配置文件不存在!" && exit 1 + listen_val=$(grep -E '^listen\s*=' ${snell_conf} | sed -E 's/^listen\s*=\s*//' | xargs) + port=$(echo "$listen_val" | awk -F',' '{print $1}' | awk -F':' '{print $NF}' | xargs) + ipv6=$(cat ${snell_conf}|grep 'ipv6 = '|awk -F 'ipv6 = ' '{print $NF}') + psk=$(cat ${snell_conf}|grep 'psk = '|awk -F 'psk = ' '{print $NF}') + obfs=$(cat ${snell_conf}|grep 'obfs = '|awk -F 'obfs = ' '{print $NF}') + host=$(cat ${snell_conf}|grep 'obfs-host = '|awk -F 'obfs-host = ' '{print $NF}') + tfo=$(cat ${snell_conf}|grep 'tfo = '|awk -F 'tfo = ' '{print $NF}') + dns=$(cat ${snell_conf}|grep 'dns = '|awk -F 'dns = ' '{print $NF}') + ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') + dns_ip_pref=$(cat ${snell_conf}|grep 'dns-ip-preference = '|awk -F 'dns-ip-preference = ' '{print $NF}') + [[ -z "$dns_ip_pref" && "$ver" == "6" ]] && dns_ip_pref="default" + mode=$(cat ${snell_conf}|grep 'mode = '|awk -F 'mode = ' '{print $NF}') + [[ -z "$mode" && "$ver" == "6" ]] && mode="default" + egress_interface=$(cat ${snell_conf}|grep 'egress-interface = '|awk -F 'egress-interface = ' '{print $NF}') +} + +# 设置端口 +setPort(){ + local orig_port="$port" + while true; do + echo -e "${Tip} 本步骤不涉及系统防火墙端口操作,请手动放行相应端口!" + echo -e "请输入 Snell Server 端口${Yellow_font_prefix}[1-65535]${Font_color_suffix}" + local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}: 2345):" + [[ -n "$port" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${port} | ${Green_font_prefix}默认${Font_color_suffix}: 2345):" + echo -e -n "${p_prompt}" + read -e input_port + [[ -z "${input_port}" ]] && input_port="2345" + port=$input_port + if [[ "${ver}" == "6" || "$current_installed_ver" == "6" ]]; then + listen_val="0.0.0.0:${port},[::]:${port}" + else + listen_val="::0:${port}" + fi + if [[ $port =~ ^[0-9]+$ ]] && [[ $port -ge 1 && $port -le 65535 ]]; then + if [[ "$port" != "$orig_port" ]] && ss -tuln | grep -q ":$port "; then + echo -e "${Error} 端口 $port 已被占用,请选择其他端口。" + else + echo && echo "==============================" + echo -e "端口 : ${Red_background_prefix} ${port} ${Font_color_suffix}" + echo "==============================" && echo + break + fi + else + echo "输入错误, 请输入正确的端口号。" + sleep 2s + setPort + fi + done +} + + +# 设置 IPv6 +setIpv6(){ + echo -e "是否开启 IPv6 解析? +================================== +${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭 +==================================" + local current_opt="2" + [[ "$ipv6" == "true" ]] && current_opt="1" + local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}:2.关闭):" + [[ -n "$ipv6" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_opt}.${ipv6} | ${Green_font_prefix}默认${Font_color_suffix}:2.关闭):" + echo -e -n "${p_prompt}" + read -e input_ipv6 + [[ -z "${input_ipv6}" ]] && input_ipv6="2" + if [[ ${input_ipv6} == "1" ]]; then + ipv6=true + else + ipv6=false + fi + echo && echo "==================================" + echo -e "IPv6 解析 开启状态:${Red_background_prefix} ${ipv6} ${Font_color_suffix}" + echo "==================================" && echo +} + +# 设置密钥 +setPSK(){ + echo "请输入 Snell Server 密钥 [0-9][a-z][A-Z] " + + local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}: 随机生成):" + [[ -n "$psk" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${psk} | ${Green_font_prefix}默认${Font_color_suffix}: 随机生成):" + + if [[ "$ver" == "6" || "$current_installed_ver" == "6" ]]; then + echo -e "${Tip} 当前目标协议为 Snell v6,密钥长度要求在 16-255 位之间" + while true; do + echo -e -n "${p_prompt}" + read -e input_psk + if [[ -z "${input_psk}" ]]; then + psk=$(tr -dc A-Za-z0-9 /dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${old_ver}" > ${snell_version_file} + echo -e "${Info} 已回滚到 Snell v${old_ver} 版本" + fi + systemctl start snell-server + return 1 +} + +# 修改配置 +setConfig(){ + checkInstalledStatus + local current_installed_ver=$(cat ${snell_version_file} | sed 's/^v//' | awk -F. '{print $1}') + [[ -z "$current_installed_ver" ]] && current_installed_ver=$ver + + echo && echo -e "请输入要操作配置项的序号,然后回车 +==============================" + echo -e " ${Green_font_prefix}1.${Font_color_suffix} 修改 端口" + echo -e " ${Green_font_prefix}2.${Font_color_suffix} 修改 密钥" + if [[ "$current_installed_ver" != "6" ]]; then + echo -e " ${Green_font_prefix}3.${Font_color_suffix} 配置 OBFS" + echo -e " ${Green_font_prefix}4.${Font_color_suffix} 配置 OBFS 域名" + echo -e " ${Green_font_prefix}5.${Font_color_suffix} 开关 IPv6 解析" + fi + echo -e " ${Green_font_prefix}6.${Font_color_suffix} 开关 TCP Fast Open" + echo -e " ${Green_font_prefix}7.${Font_color_suffix} 配置 DNS" + echo -e " ${Green_font_prefix}8.${Font_color_suffix} 配置 Snell 协议版本" + if [[ "$current_installed_ver" == "6" ]]; then + echo -e " ${Green_font_prefix}9.${Font_color_suffix} 配置 DNS IP 偏好" + echo -e "${Green_font_prefix}10.${Font_color_suffix} 配置 混淆模式" + fi + echo -e "==============================" + echo -e "${Green_font_prefix}11.${Font_color_suffix} 修改 全部配置" && echo + read -e -p "(默认: 取消):" modify + [[ -z "${modify}" ]] && echo "已取消..." && exit 1 + if [[ "${modify}" == "1" ]]; then + readConfig + setPort + writeConfig + restartSnell + elif [[ "${modify}" == "2" ]]; then + readConfig + setPSK + writeConfig + restartSnell + elif [[ "${modify}" == "3" ]]; then + readConfig + setObfs # 在 setObfs 中已处理 host + writeConfig + restartSnell + elif [[ "${modify}" == "4" ]]; then + readConfig + if [[ ${obfs} == "off" ]]; then + echo -e "${Error} OBFS 当前为 off,无法修改 OBFS 域名。" + else + setHost + writeConfig + restartSnell + fi + elif [[ "${modify}" == "5" ]]; then + readConfig + setIpv6 + writeConfig + restartSnell + elif [[ "${modify}" == "6" ]]; then + readConfig + setTFO + writeConfig + restartSnell + elif [[ "${modify}" == "7" ]]; then + readConfig + setDNS + writeConfig + restartSnell + elif [[ "${modify}" == "8" ]]; then + readConfig + local current_installed_ver=$(cat ${snell_version_file} | sed 's/^v//' | awk -F. '{print $1}') + [[ -z "$current_installed_ver" ]] && current_installed_ver=$ver + + setVer + + if [[ "$ver" != "$current_installed_ver" ]]; then + if [[ "$ver" -gt "$current_installed_ver" ]]; then + echo -e "${Info} 协议版本将从 Snell v${current_installed_ver} 升级到 Snell v${ver}" + echo -e "确定要升级吗?(y/N)" + else + echo -e "${Info} 协议版本将从 Snell v${current_installed_ver} 降级到 Snell v${ver}" + echo -e "确定要降级吗?(y/N)" + fi + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} == [Yy] ]]; then + if [[ "$ver" == "6" ]]; then + setDNSIPPref + setMode + checkPskForV6 + else + setObfs + fi + + switchBinary "$current_installed_ver" "$ver" + if [[ $? -ne 0 ]]; then + ver=$current_installed_ver + else + writeConfig + systemctl restart snell-server + for ((i=0; i<10; i++)); do + sleep 1 + checkStatus + if [[ "$status" == "running" ]]; then + break + fi + done + if [[ "$status" != "running" ]]; then + echo -e "${Error} 协议切换后服务启动失败,以下为错误日志:" + journalctl -u snell-server -n 20 --no-pager + echo -e "${Error} 正在回滚到原版本..." + backup_file=$(ls -t "${snell_bin}".v${current_installed_ver}.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${current_installed_ver}" > ${snell_version_file} + ver=$current_installed_ver + writeConfig + systemctl start snell-server + echo -e "${Info} 已回滚到 Snell v${current_installed_ver} 版本并恢复配置" + fi + else + echo -e "${Info} Snell Server 重启完毕!" + fi + sleep 3s + startMenu + return + fi + else + echo -e "${Info} 已取消切换,保持原配置" + ver=$current_installed_ver + fi + else + echo -e "${Info} 版本未发生改变" + fi + + writeConfig + restartSnell + elif [[ "${modify}" == "9" ]]; then + readConfig + if [[ "$current_installed_ver" != "6" ]]; then + echo -e "${Error} 当前版本不是 Snell v6,不支持 DNS IP 偏好配置!" + sleep 2s; setConfig; return + fi + setDNSIPPref + writeConfig + restartSnell + elif [[ "${modify}" == "10" ]]; then + readConfig + if [[ "$current_installed_ver" != "6" ]]; then + echo -e "${Error} 当前版本不是 Snell v6,不支持 混淆模式配置!" + sleep 2s; setConfig; return + fi + setMode + writeConfig + restartSnell + elif [[ "${modify}" == "11" ]]; then + readConfig + local current_installed_ver=$(cat ${snell_version_file} | sed 's/^v//' | awk -F. '{print $1}') + [[ -z "$current_installed_ver" ]] && current_installed_ver=$ver + + setVer + + if [[ "$ver" != "$current_installed_ver" ]]; then + if [[ "$ver" -gt "$current_installed_ver" ]]; then + echo -e "${Info} 协议版本将从 Snell v${current_installed_ver} 升级到 Snell v${ver}" + echo -e "确定要升级吗?(y/N)" + else + echo -e "${Info} 协议版本将从 Snell v${current_installed_ver} 降级到 Snell v${ver}" + echo -e "确定要降级吗?(y/N)" + fi + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} != [Yy] ]]; then + echo -e "${Info} 已取消切换,保持原版本" + ver=$current_installed_ver + fi + fi + + setPort + setPSK + if [[ "$ver" != "6" ]]; then + setIpv6 + fi + setTFO + setDNS + + if [[ "$ver" == "6" ]]; then + setDNSIPPref + setMode + checkPskForV6 + else + setObfs + fi + + if [[ "$ver" != "$current_installed_ver" ]]; then + switchBinary "$current_installed_ver" "$ver" + if [[ $? -ne 0 ]]; then + ver=$current_installed_ver + writeConfig + restartSnell + else + writeConfig + systemctl restart snell-server + for ((i=0; i<10; i++)); do + sleep 1 + checkStatus + if [[ "$status" == "running" ]]; then + break + fi + done + if [[ "$status" != "running" ]]; then + echo -e "${Error} 协议切换后服务启动失败,以下为错误日志:" + journalctl -u snell-server -n 20 --no-pager + echo -e "${Error} 正在回滚到原版本..." + backup_file=$(ls -t "${snell_bin}".v${current_installed_ver}.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${current_installed_ver}" > ${snell_version_file} + ver=$current_installed_ver + writeConfig + systemctl start snell-server + echo -e "${Info} 已回滚到 v${current_installed_ver} 版本并恢复配置" + fi + else + echo -e "${Info} Snell Server 重启完毕!" + fi + sleep 3s + startMenu + return + fi + else + writeConfig + restartSnell + fi + else + echo -e "${Error} 请输入正确数字${Yellow_font_prefix}[1-11]${Font_color_suffix}" + sleep 2s + setConfig + fi + sleep 3s + startMenu +} + + +# 安装 Snell v2 +installSnellV2(){ + checkRoot + [[ -e ${snell_bin} ]] && echo -e "${Error} 检测到 Snell Server 已安装!" && exit 1 + echo -e "${Info} 开始设置 配置..." + setPort + setPSK + setObfs + setIpv6 + setTFO + echo -e "${Info} 开始安装/配置 依赖..." + checkDependencies + installDependencies + echo -e "${Info} 开始下载/安装..." + downloadSnellV2 + echo -e "${Info} 开始安装 服务脚本..." + setupService + echo -e "${Info} 开始写入 配置文件..." + writeConfig + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + startSnell + echo -e "${Info} 启动完成,查看配置..." + viewConfig +} + +# 安装 Snell v3 +installSnellV3(){ + checkRoot + [[ -e ${snell_bin} ]] && echo -e "${Error} 检测到 Snell Server 已安装!" && exit 1 + echo -e "${Info} 开始设置 配置..." + setPort + setPSK + setObfs + setIpv6 + setTFO + echo -e "${Info} 开始安装/配置 依赖..." + checkDependencies + installDependencies + echo -e "${Info} 开始下载/安装..." + downloadSnellV3 + echo -e "${Info} 开始安装 服务脚本..." + setupService + echo -e "${Info} 开始写入 配置文件..." + writeConfig + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + startSnell + echo -e "${Info} 启动完成,查看配置..." + viewConfig +} + +# 安装 Snell v4 +installSnellV4(){ + checkRoot + [[ -e ${snell_bin} ]] && echo -e "${Error} 检测到 Snell Server 已安装,请先卸载旧版再安装新版!" && exit 1 + echo -e "${Info} 开始设置 配置..." + setPort + setPSK + setObfs + setIpv6 + setTFO + setDNS + echo -e "${Info} 开始安装/配置 依赖..." + checkDependencies + installDependencies + echo -e "${Info} 开始下载/安装..." + downloadSnellV4 + echo -e "${Info} 开始安装 服务脚本..." + setupService + echo -e "${Info} 开始写入 配置文件..." + writeConfig + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + startSnell + echo -e "${Info} 启动完成,查看配置..." + viewConfig +} + +# 安装 Snell v5 +installSnellV5(){ + checkRoot + [[ -e ${snell_bin} ]] && echo -e "${Error} 检测到 Snell Server 已安装,请先卸载旧版再安装新版!" && exit 1 + echo -e "${Info} 开始设置 配置..." + setPort + setPSK + setObfs + setIpv6 + setTFO + setDNS + echo -e "${Info} 开始安装/配置 依赖..." + checkDependencies + installDependencies + echo -e "${Info} 开始下载/安装..." + downloadSnellV5 + echo -e "${Info} 开始安装 服务脚本..." + setupService + echo -e "${Info} 开始写入 配置文件..." + writeConfig + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + startSnell + echo -e "${Info} 启动完成,查看配置..." + viewConfig +} + + +# 下载并安装 Snell v6(官方源) +downloadSnellV6(){ + downloadSnell "${snell_v6_version}" "Snell v6 官网源版" +} + +# 安装 Snell v6 +installSnellV6(){ + checkRoot + [[ -e ${snell_bin} ]] && echo -e "${Error} 检测到 Snell Server 已安装,请先卸载旧版再安装新版!" && exit 1 + echo -e "${Info} 开始设置 配置..." + setPort + setPSK + setTFO + setDNS + setDNSIPPref + setMode + echo -e "${Info} 开始安装/配置 依赖..." + checkDependencies + installDependencies + echo -e "${Info} 开始下载/安装..." + downloadSnellV6 + echo -e "${Info} 开始安装 服务脚本..." + setupService + echo -e "${Info} 开始写入 配置文件..." + writeConfig + echo -e "${Info} 所有步骤 安装完毕,开始启动..." + startSnell + echo -e "${Info} 启动完成,查看配置..." + viewConfig +} +# 启动 Snell +startSnell(){ + checkInstalledStatus + checkStatus + if [[ "$status" == "running" ]]; then + echo -e "${Info} Snell Server 已在运行!" + else + echo -e "${Info} 正在启动 Snell Server..." + systemctl start snell-server + + # 等待启动完成,最多等待10秒 + local timeout=10 + local elapsed=0 + while [[ $elapsed -lt $timeout ]]; do + sleep 1 + checkStatus + if [[ "$status" == "running" ]]; then + echo -e "${Info} Snell Server 启动成功!" + return 0 + fi + ((elapsed++)) + done + + # 如果5秒后仍未启动,检查状态并报错 + checkStatus + if [[ "$status" == "running" ]]; then + echo -e "${Info} Snell Server 启动成功!" + else + echo -e "${Error} Snell Server 启动失败!" + echo -e "${Error} 请使用 'systemctl status snell-server' 查看详细错误信息" + journalctl -u snell-server -n 20 --no-pager + exit 1 + fi + fi +} + +# 停止 Snell +stopSnell(){ + checkInstalledStatus + checkStatus + [[ !"$status" == "running" ]] && echo -e "${Error} Snell Server 没有运行,请检查!" && exit 1 + systemctl stop snell-server + echo -e "${Info} Snell Server 停止成功!" + sleep 3s + startMenu +} + +# 重启 Snell +restartSnell(){ + checkInstalledStatus + systemctl restart snell-server + echo -e "${Info} Snell Server 重启完毕!" + sleep 3s + startMenu +} + +# 更新 Snell(占位,待实现) +updateSnell(){ + checkInstalledStatus + echo -e "${Info} Snell Server 更新完毕!" + sleep 3s + startMenu +} + +# Snell v2 更新到 Snell v3 +updateV2toV3(){ + checkInstalledStatus + readConfig + + # 检查当前版本是否为 Snell v2 + if [[ "$ver" != "2" ]]; then + echo -e "${Error} 当前版本不是 Snell v2,无法使用此功能!当前版本:Snell v${ver}" + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 即将将 Snell Server 从 Snell v2 更新到 Snell v3 版本" + echo -e "确定要更新吗?(y/N)" + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} != [Yy] ]]; then + echo -e "${Info} 已取消更新" + sleep 2s + startMenu + return 0 + fi + + echo -e "${Info} 开始更新 Snell v2 到 Snell v3..." + + # 停止服务 + echo -e "${Info} 停止 Snell Server 服务..." + systemctl stop snell-server + + # 备份当前二进制文件 + if [[ -e "${snell_bin}" ]]; then + echo -e "${Info} 备份当前程序文件..." + cp "${snell_bin}" "${snell_bin}.v2.backup.$(date +%Y%m%d_%H%M%S)" + fi + + echo -e "${Info} 开始下载 Snell v3 版本..." + downloadSnellV3 + + if [[ $? -eq 0 ]]; then + # 更新配置文件中的版本号 + echo -e "${Info} 更新配置文件版本号..." + sed -i "s/version = 2/version = 3/g" "${snell_conf}" + + # 重新加载 systemd 并启动服务 + echo -e "${Info} 重启 Snell Server 服务..." + systemctl daemon-reload + systemctl start snell-server + + # 检查服务状态 + sleep 2 + checkStatus + if [[ "$status" == "running" ]]; then + echo -e "${Info} Snell v2 到 Snell v3 更新成功!" + echo -e "${Info} 当前版本:Snell v3" + else + echo -e "${Error} 服务启动失败,正在回滚..." + # 回滚到 Snell v2 + backup_file=$(ls -t "${snell_bin}".v2.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + sed -i "s/version = 3/version = 2/g" "${snell_conf}" + systemctl start snell-server + echo -e "${Info} 已回滚到 Snell v2 版本" + fi + fi + else + echo -e "${Error} Snell v3 下载失败,保持 Snell v2 版本" + systemctl start snell-server + fi + + sleep 3s + startMenu +} + +# Snell v3 更新到 Snell v4 +updateV3toV4(){ + checkInstalledStatus + readConfig + + # 检查当前版本是否为 Snell v3 + if [[ "$ver" != "3" ]]; then + echo -e "${Error} 当前版本不是 Snell v3,无法使用此功能!当前版本:Snell v${ver}" + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 即将将 Snell Server 从 Snell v3 更新到 Snell v4 版本" + echo -e "确定要更新吗?(y/N)" + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} != [Yy] ]]; then + echo -e "${Info} 已取消更新" + sleep 2s + startMenu + return 0 + fi + + echo -e "${Info} 开始更新 Snell v3 到 Snell v4..." + + # 停止服务 + echo -e "${Info} 停止 Snell Server 服务..." + systemctl stop snell-server + + # 备份当前二进制文件 + if [[ -e "${snell_bin}" ]]; then + echo -e "${Info} 备份当前程序文件..." + cp "${snell_bin}" "${snell_bin}.v3.backup.$(date +%Y%m%d_%H%M%S)" + fi + + # 获取最新的 Snell v4 版本号 + web_v4_version=$(getLatestVersionFromWeb "v4") + script_v4_version="${snell_v4_version}" + + target_v4_version="" + if [[ -n "$web_v4_version" ]] && validateVersionUrl "$web_v4_version"; then + target_v4_version="$web_v4_version" + echo -e "${Info} 使用网页获取的 Snell v4 版本: v${target_v4_version}" + elif [[ -n "$script_v4_version" ]] && validateVersionUrl "$script_v4_version"; then + target_v4_version="$script_v4_version" + echo -e "${Info} 使用脚本内置的 Snell v4 版本: v${target_v4_version}" + fi + + if [[ -z "$target_v4_version" ]]; then + echo -e "${Error} 无法找到有效的 Snell v4 版本进行更新" + systemctl start snell-server + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 开始下载 Snell v4 版本..." + downloadSnell "${target_v4_version}" "Snell v4 版本" true "3.0.1" + + if [[ $? -eq 0 ]]; then + actual_version=$(cat ${snell_version_file} | sed 's/^v//') + if [[ "$actual_version" =~ ^4\. ]]; then + echo -e "${Info} 更新配置文件版本号..." + sed -i "s/version = 3/version = 4/g" "${snell_conf}" + else + echo -e "${Tip} 注意:由于 Snell v4 下载链接问题,已回退重新安装 Snell v${actual_version} 版本" + fi + + # 重新加载 systemd 并启动服务 + echo -e "${Info} 重启 Snell Server 服务..." + systemctl daemon-reload + systemctl start snell-server + + # 检查服务状态 + sleep 2 + checkStatus + if [[ "$status" == "running" ]]; then + if [[ "$actual_version" =~ ^4\. ]]; then + echo -e "${Info} Snell v3 到 Snell v4 更新成功!" + echo -e "${Info} 当前版本:Snell v${actual_version}" + fi + else + echo -e "${Error} 服务启动失败,正在回滚..." + # 回滚到 Snell v3 + backup_file=$(ls -t "${snell_bin}".v3.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v3.0.1" > ${snell_version_file} + sed -i "s/version = 4/version = 3/g" "${snell_conf}" + systemctl start snell-server + echo -e "${Info} 已回滚到 Snell v3 版本" + fi + fi + else + echo -e "${Error} Snell v4 下载失败,保持 Snell v3 版本" + systemctl start snell-server + fi + + sleep 3s + startMenu +} + +# Snell v4 更新到 Snell v5 +updateV4toV5(){ + checkInstalledStatus + readConfig + + # 检查当前版本是否为 Snell v4 + if [[ "$ver" != "4" ]]; then + echo -e "${Error} 当前版本不是 Snell v4,无法使用此功能!当前版本:Snell v${ver}" + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 即将将 Snell Server 从 Snell v4 更新到 Snell v5 版本" + echo -e "确定要更新吗?(y/N)" + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} != [Yy] ]]; then + echo -e "${Info} 已取消更新" + sleep 2s + startMenu + return 0 + fi + + echo -e "${Info} 开始更新 Snell v4 到 Snell v5..." + + # 停止服务 + echo -e "${Info} 停止 Snell Server 服务..." + systemctl stop snell-server + + # 备份当前二进制文件 + if [[ -e "${snell_bin}" ]]; then + echo -e "${Info} 备份当前程序文件..." + cp "${snell_bin}" "${snell_bin}.v4.backup.$(date +%Y%m%d_%H%M%S)" + fi + + # 获取当前安装的 Snell v4 版本作为回退版本 + current_v4_version=$(cat ${snell_version_file} | sed 's/^v//') + + # 获取最新的 Snell v5 版本号(优先使用网页版本,然后是脚本内置版本) + web_v5_version=$(getLatestVersionFromWeb "v5") + script_v5_version="${snell_v5_version}" + + # 选择最新的 Snell v5 版本 + target_v5_version="" + if [[ -n "$web_v5_version" ]]; then + if validateVersionUrl "$web_v5_version"; then + target_v5_version="$web_v5_version" + echo -e "${Info} 使用网页获取的 Snell v5 版本: v${target_v5_version}" + fi + fi + + # 如果网页版本无效,尝试脚本内置版本 + if [[ -z "$target_v5_version" && -n "$script_v5_version" ]]; then + if validateVersionUrl "$script_v5_version"; then + target_v5_version="$script_v5_version" + echo -e "${Info} 使用脚本内置的 Snell v5 版本: v${target_v5_version}" + fi + fi + + # 如果都无效,取消更新 + if [[ -z "$target_v5_version" ]]; then + echo -e "${Error} 无法找到有效的 Snell v5 版本进行更新" + systemctl start snell-server + sleep 3s + startMenu + return 1 + fi + + # 下载并安装 Snell v5,启用回退机制 + echo -e "${Info} 开始下载 Snell v5 版本..." + downloadSnell "${target_v5_version}" "Snell v5 版本" true "${current_v4_version}" + + if [[ $? -eq 0 ]]; then + actual_version=$(cat ${snell_version_file} | sed 's/^v//') + + if [[ "$actual_version" =~ ^5\. ]]; then + # 更新配置文件中的版本号 + echo -e "${Info} 更新配置文件版本号..." + sed -i "s/version = 4/version = 5/g" "${snell_conf}" + else + echo -e "${Tip} 注意:由于 Snell v5 下载链接问题,已回退重新安装 Snell v${actual_version} 版本" + fi + + # 重新加载 systemd 并启动服务 + echo -e "${Info} 重启 Snell Server 服务..." + systemctl daemon-reload + systemctl start snell-server + + # 检查服务状态 + sleep 2 + checkStatus + if [[ "$status" == "running" ]]; then + if [[ "$actual_version" =~ ^5\. ]]; then + echo -e "${Info} Snell v4 到 Snell v5 更新成功!" + echo -e "${Info} 当前版本:Snell v${actual_version}" + fi + else + echo -e "${Error} 服务启动失败,正在回滚..." + # 回滚到 Snell v4 + backup_file=$(ls -t "${snell_bin}".v4.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${current_v4_version}" > ${snell_version_file} + sed -i "s/version = 5/version = 4/g" "${snell_conf}" + systemctl start snell-server + echo -e "${Info} 已回滚到 Snell v4 版本" + fi + fi + else + echo -e "${Error} Snell v5 下载失败,保持 Snell v4 版本" + systemctl start snell-server + fi + + sleep 3s + startMenu +} + + +# Snell v5 更新到 Snell v6 +updateV5toV6(){ + checkInstalledStatus + readConfig + + if [[ "$ver" != "5" ]]; then + echo -e "${Error} 当前版本不是 Snell v5,无法使用此功能!当前版本:Snell v${ver}" + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 即将将 Snell Server 从 Snell v5 更新到 Snell v6 版本" + echo -e "确定要更新吗?(y/N)" + read -e -p "(默认: n):" confirm + [[ -z "${confirm}" ]] && confirm="n" + + if [[ ${confirm} != [Yy] ]]; then + echo -e "${Info} 已取消更新" + sleep 2s + startMenu + return 0 + fi + + echo -e "${Info} 开始更新 Snell v5 到 Snell v6..." + + systemctl stop snell-server + + if [[ -e "${snell_bin}" ]]; then + echo -e "${Info} 备份当前程序文件..." + cp "${snell_bin}" "${snell_bin}.v5.backup.$(date +%Y%m%d_%H%M%S)" + fi + + current_v5_version=$(cat ${snell_version_file} | sed 's/^v//') + web_v6_version=$(getLatestVersionFromWeb "v6") + script_v6_version="${snell_v6_version}" + + target_v6_version="" + if [[ -n "$web_v6_version" ]]; then + if validateVersionUrl "$web_v6_version"; then + target_v6_version="$web_v6_version" + echo -e "${Info} 使用网页获取的 Snell v6 版本: v${target_v6_version}" + fi + fi + + if [[ -z "$target_v6_version" && -n "$script_v6_version" ]]; then + if validateVersionUrl "$script_v6_version"; then + target_v6_version="$script_v6_version" + echo -e "${Info} 使用脚本内置的 Snell v6 版本: v${target_v6_version}" + fi + fi + + if [[ -z "$target_v6_version" ]]; then + echo -e "${Error} 无法找到有效的 Snell v6 版本进行更新" + systemctl start snell-server + sleep 3s + startMenu + return 1 + fi + + echo -e "${Info} 开始下载 Snell v6 版本..." + downloadSnell "${target_v6_version}" "Snell v6 版本" true "${current_v5_version}" + + if [[ $? -eq 0 ]]; then + actual_version=$(cat ${snell_version_file} | sed 's/^v//') + + if [[ "$actual_version" =~ ^6\. ]]; then + echo -e "${Info} 更新配置文件版本号..." + ver=6 + setDNSIPPref + setMode + checkPskForV6 + writeConfig + else + echo -e "${Tip} 注意:由于 Snell v6 下载链接问题,已回退重新安装 Snell v${actual_version} 版本" + fi + + systemctl daemon-reload + systemctl start snell-server + + for ((i=0; i<10; i++)); do + sleep 1 + checkStatus + if [[ "$status" == "running" ]]; then + break + fi + done + if [[ "$status" == "running" ]]; then + if [[ "$actual_version" =~ ^6\. ]]; then + echo -e "${Info} Snell v5 到 Snell v6 更新成功!" + echo -e "${Info} 当前版本:Snell v${actual_version}" + fi + else + echo -e "${Error} 升级后服务启动失败,以下为错误日志:" + journalctl -u snell-server -n 20 --no-pager + echo -e "${Error} 正在回滚..." + backup_file=$(ls -t "${snell_bin}".v5.backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${current_v5_version}" > ${snell_version_file} + ver=5 + writeConfig + systemctl start snell-server + echo -e "${Info} 已回滚到 Snell v5 版本" + fi + fi + else + echo -e "${Error} Snell v6 下载失败,保持 Snell v5 版本" + systemctl start snell-server + fi + + sleep 3s + startMenu +} +# 更新 Snell Server 到最新版本 +updateSnellServer(){ + checkInstalledStatus + readConfig + + echo -e "${Info} 准备更新 Snell Server..." + + # 显示详细的版本检查信息 + echo -e "${Info} 正在检查版本信息..." + updateBuiltinVersions true + checkVersionUpdate true + + # 检查是否有更新可用 + force_checked=false + if [[ "$update_available" != true ]]; then + echo -e "${Info} 当前已是最新版本,无需更新!" + echo -e "${Info} 当前版本: ${Green_font_prefix}v${current_installed_version}${Font_color_suffix}" + echo + echo -e "${Tip} 是否要强制重新检查最新版本?(y/N)" + read -e -p "(默认: n):" force_check + [[ -z "${force_check}" ]] && force_check="n" + + if [[ ${force_check} == [Yy] ]]; then + echo -e "${Info} 强制重新检查最新版本..." + # 清除缓存并重新检查 + rm -f /tmp/snell_version_cache + updateBuiltinVersions true + checkVersionUpdate true + force_checked=true + + # 重新检查后如果有更新,继续更新流程 + if [[ "$update_available" == true ]]; then + echo -e "${Info} 检测到新版本,继续更新流程..." + else + echo -e "${Info} 重新检查后仍为最新版本" + sleep 3s + startMenu + return 0 + fi + else + sleep 3s + startMenu + return 0 + fi + fi + + # 显示版本信息 + echo -e "${Info} 当前版本: ${Yellow_font_prefix}v${current_installed_version}${Font_color_suffix}" + echo -e "${Info} 最新版本: ${Green_font_prefix}v${latest_available_version}${Font_color_suffix}" + echo -e "确定要更新吗?(Y/n)" + read -e -p "(默认: y):" confirm + [[ -z "${confirm}" ]] && confirm="y" + + if [[ ${confirm} == [Nn] ]]; then + echo -e "${Info} 已取消更新" + sleep 2s + startMenu + return 0 + fi + + echo -e "${Info} 开始更新 Snell Server 到最新版本..." + + # 停止服务 + echo -e "${Info} 停止 Snell Server 服务..." + systemctl stop snell-server + + # 备份当前二进制文件 + if [[ -e "${snell_bin}" ]]; then + echo -e "${Info} 备份当前程序文件..." + cp "${snell_bin}" "${snell_bin}.backup.$(date +%Y%m%d_%H%M%S)" + fi + + # 根据版本选择下载函数,启用回退机制 + echo -e "${Info} 开始下载最新版本..." + case "$ver" in + "6") + downloadSnell "${latest_available_version}" "Snell v6 最新版" true "${current_installed_version}" + ;; + *) + echo -e "${Error} 不支持的版本: Snell v${ver}" + systemctl start snell-server + sleep 3s + startMenu + return 1 + ;; + esac + + if [[ $? -eq 0 ]]; then + # 重新加载 systemd 并启动服务 + echo -e "${Info} 重启 Snell Server 服务..." + systemctl daemon-reload + systemctl start snell-server + + # 检查服务状态 + sleep 2 + checkStatus + if [[ "$status" == "running" ]]; then + actual_version=$(cat ${snell_version_file} | sed 's/^v//') + echo -e "${Info} Snell Server 更新成功!" + echo -e "${Info} 当前版本:v${actual_version}" + + # 如果实际更新版本与预期不同,给出提示 + if [[ "$actual_version" != "$latest_available_version" ]]; then + echo -e "${Tip} 注意:由于下载链接问题,已回退到 v${actual_version} 版本" + fi + else + echo -e "${Error} 服务启动失败,正在回滚..." + # 回滚到备份版本 + backup_file=$(ls -t "${snell_bin}".backup.* 2>/dev/null | head -1) + if [[ -n "$backup_file" && -e "$backup_file" ]]; then + cp "$backup_file" "${snell_bin}" + echo "v${current_installed_version}" > ${snell_version_file} + systemctl start snell-server + echo -e "${Info} 已回滚到备份版本 v${current_installed_version}" + fi + fi + else + echo -e "${Error} 下载失败,启动原版本" + systemctl start snell-server + fi + + sleep 3s + startMenu +} + +# 自动获取 Snell 最新版本号 +getLatestVersionFromWeb(){ + local version_type=$1 + local release_page="https://kb.nssurge.com/surge-knowledge-base/zh/release-notes/snell" + + page_content=$(curl -s -L --max-time 10 "$release_page" 2>/dev/null) + + if [[ -z "$page_content" ]]; then + return 1 + fi + + if [[ "$version_type" == "v4" ]]; then + latest_v4=$(echo "$page_content" | grep -oE "snell-server-v4\.[0-9]+\.[0-9]+-linux" | head -1 | sed 's/snell-server-v//g' | sed 's/-linux//g') + if [[ -n "$latest_v4" ]]; then + echo "$latest_v4" + return 0 + fi + elif [[ "$version_type" == "v5" ]]; then + latest_v5=$(echo "$page_content" | grep -oE "snell-server-v5\.[0-9]+\.[0-9]+[a-z]*[0-9]*-linux" | head -1 | sed 's/snell-server-v//g' | sed 's/-linux//g') + if [[ -n "$latest_v5" ]]; then + echo "$latest_v5" + return 0 + fi + elif [[ "$version_type" == "v6" ]]; then + latest_v6=$(echo "$page_content" | grep -oE "snell-server-v6\.[0-9]+\.[0-9]+[a-z]*[0-9]*-linux" | head -1 | sed 's/snell-server-v//g' | sed 's/-linux//g') + if [[ -n "$latest_v6" ]]; then + echo "$latest_v6" + return 0 + fi + fi + + return 1 +} + +# 更新脚本内置版本号(带缓存机制,但保持脚本版本优先级) +updateBuiltinVersions(){ + local show_info=${1:-false} # 是否显示详细信息,默认为静默 + local cache_file="/tmp/snell_version_cache" + local cache_time=3600 + local current_time=$(date +%s) + + # Snell v2 和 Snell v3 始终使用固定版本,无需检查 + web_v2_newer=false + web_v3_newer=false + + # 检查缓存是否存在且有效 + if [[ -f "$cache_file" ]]; then + local cache_timestamp=$(head -1 "$cache_file" 2>/dev/null) + if [[ -n "$cache_timestamp" && $((current_time - cache_timestamp)) -lt $cache_time ]]; then + local cached_v4=$(sed -n '2p' "$cache_file" 2>/dev/null) + local cached_v5=$(sed -n '3p' "$cache_file" 2>/dev/null) + local cached_v6=$(sed -n '4p' "$cache_file" 2>/dev/null) + if [[ -n "$cached_v4" && -n "$cached_v5" && -n "$cached_v6" ]]; then + # 检查网页版本是否比脚本内置版本更新 + compareVersions "${snell_v4_version}" "$cached_v4" + if [[ $? -eq 2 ]]; then + web_v4_newer=true + else + web_v4_newer=false + fi + + compareVersions "${snell_v5_version}" "$cached_v5" + if [[ $? -eq 2 ]]; then + web_v5_newer=true + else + web_v5_newer=false + fi + + compareVersions "${snell_v6_version}" "$cached_v6" + if [[ $? -eq 2 ]]; then + web_v6_newer=true + else + web_v6_newer=false + fi + + return 0 + fi + fi + fi + + [[ "$show_info" == true ]] && echo -e "${Info} 正在检查官方最新版本..." + + # 获取最新的 Snell v4 版本 + local latest_v4_web + latest_v4_web=$(getLatestVersionFromWeb "v4") + if [[ $? -eq 0 && -n "$latest_v4_web" ]]; then + # 比较网页版本和脚本内置版本 + compareVersions "${snell_v4_version}" "$latest_v4_web" + if [[ $? -eq 2 ]]; then + web_v4_newer=true + else + web_v4_newer=false + fi + else + web_v4_newer=false + latest_v4_web="${snell_v4_version}" + fi + + # 获取最新的 Snell v5 版本 + local latest_v5_web + latest_v5_web=$(getLatestVersionFromWeb "v5") + if [[ $? -eq 0 && -n "$latest_v5_web" ]]; then + # 比较网页版本和脚本内置版本 + compareVersions "${snell_v5_version}" "$latest_v5_web" + if [[ $? -eq 2 ]]; then + web_v5_newer=true + else + web_v5_newer=false + fi + else + web_v5_newer=false + latest_v5_web="${snell_v5_version}" + fi + + # 获取最新的 Snell v6 版本 + local latest_v6_web + latest_v6_web=$(getLatestVersionFromWeb "v6") + if [[ $? -eq 0 && -n "$latest_v6_web" ]]; then + # 比较网页版本和脚本内置版本 + compareVersions "${snell_v6_version}" "$latest_v6_web" + if [[ $? -eq 2 ]]; then + web_v6_newer=true + else + web_v6_newer=false + fi + else + web_v6_newer=false + latest_v6_web="${snell_v6_version}" + fi + + # 更新缓存(缓存 Snell v4, Snell v5, Snell v6) + echo "$current_time" > "$cache_file" + echo "$latest_v4_web" >> "$cache_file" + echo "$latest_v5_web" >> "$cache_file" + echo "$latest_v6_web" >> "$cache_file" +} + +# 强制检查最新版本(清除缓存) +forceCheckVersions(){ + echo -e "${Info} 强制检查 Snell 最新版本..." + + rm -f "/tmp/snell_version_cache" + updateBuiltinVersions true + + echo -e "${Info} 版本检查完成!" + echo -e "${Info} 脚本内置 Snell v4 版本: ${Green_font_prefix}${snell_v4_version}${Font_color_suffix}" + echo -e "${Info} 脚本内置 Snell v5 版本: ${Green_font_prefix}${snell_v5_version}${Font_color_suffix}" + echo -e "${Info} 脚本内置 Snell v6 版本: ${Green_font_prefix}${snell_v6_version}${Font_color_suffix}" + + # 获取网页版本进行对比 + web_v4=$(getLatestVersionFromWeb "v4") + web_v5=$(getLatestVersionFromWeb "v5") + web_v6=$(getLatestVersionFromWeb "v6") + + if [[ -n "$web_v4" ]]; then + echo -e "${Info} 网页获取 Snell v4 版本: ${Yellow_font_prefix}${web_v4}${Font_color_suffix}" + compareVersions "${snell_v4_version}" "$web_v4" + case $? in + 1) echo -e "${Info} Snell v4 版本状态: 脚本内置版本与网页版本相同" ;; + 0) echo -e "${Info} Snell v4 版本状态: 脚本内置版本比网页版本更新" ;; + 2) echo -e "${Tip} Snell v4 版本状态: 网页版本比脚本内置版本更新" ;; + esac + fi + + if [[ -n "$web_v5" ]]; then + echo -e "${Info} 网页获取 Snell v5 版本: ${Yellow_font_prefix}${web_v5}${Font_color_suffix}" + compareVersions "${snell_v5_version}" "$web_v5" + case $? in + 1) echo -e "${Info} Snell v5 版本状态: 脚本内置版本与网页版本相同" ;; + 0) echo -e "${Info} Snell v5 版本状态: 脚本内置版本比网页版本更新" ;; + 2) echo -e "${Tip} Snell v5 版本状态: 网页版本比脚本内置版本更新" ;; + esac + fi + + if [[ -n "$web_v6" ]]; then + echo -e "${Info} 网页获取 Snell v6 版本: ${Yellow_font_prefix}${web_v6}${Font_color_suffix}" + compareVersions "${snell_v6_version}" "$web_v6" + case $? in + 1) echo -e "${Info} Snell v6 版本状态: 脚本内置版本与网页版本相同" ;; + 0) echo -e "${Info} Snell v6 版本状态: 脚本内置版本比网页版本更新" ;; + 2) echo -e "${Tip} Snell v6 版本状态: 网页版本比脚本内置版本更新" ;; + esac + fi + + sleep 3s + startMenu +} + +# 卸载 Snell +uninstallSnell(){ + checkInstalledStatus + echo "确定要卸载 Snell Server ? (y/N)" + echo + read -e -p "(默认: n):" unyn + [[ -z ${unyn} ]] && unyn="n" + if [[ ${unyn} == [Yy] ]]; then + echo -e "${Info} 停止并禁用服务..." + systemctl stop snell-server + systemctl disable snell-server + + echo -e "${Info} 移除主程序..." + rm -rf "${snell_bin}" + + echo -e "${Info} 移除 systemd 服务文件..." + rm -f /etc/systemd/system/snell-server.service + systemctl daemon-reload + + if [[ -f "${sysctl_conf}" ]]; then + echo -e "${Tip} 由于网络优化配置可能被其他程序共用,卸载过程未移除网络优化配置,如需彻底移除可手动删除:${sysctl_conf}" + fi + + echo -e "${Info} 移除配置文件及版本记录..." + rm -rf /etc/snell + echo && echo "Snell Server 卸载完成!" && echo + else + echo && echo "卸载已取消..." && echo + fi + sleep 3s + startMenu +} + +# 获取 IPv4 地址 +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 +} + +# 获取 IPv6 地址 +getIpv6(){ + ip6=$(wget -qO- -6 -t1 -T2 ifconfig.co) + if [[ -z "${ip6}" ]]; then + ip6="IPv6_Error" + fi +} + +# 查看配置信息 +viewConfig(){ + checkInstalledStatus + readConfig + getIpv4 + getIpv6 + clear && echo + echo -e "Snell Server 配置信息:" + echo -e "——————————————————————————————————————————————————" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + echo -e " IPv4 地址\t: ${Green_font_prefix}${ipv4}${Font_color_suffix}" + fi + if [[ "${ip6}" != "IPv6_Error" ]]; then + echo -e " IPv6 地址\t: ${Green_font_prefix}${ip6}${Font_color_suffix}" + fi + echo -e " 端口\t\t: ${Green_font_prefix}${port}${Font_color_suffix}" + echo -e " 密钥\t\t: ${Green_font_prefix}${psk}${Font_color_suffix}" + + if [[ "$ver" != "6" ]]; then + echo -e " OBFS\t\t: ${Green_font_prefix}${obfs}${Font_color_suffix}" + if [[ "$obfs" != "off" && -n "$host" ]]; then + echo -e " 域名\t\t: ${Green_font_prefix}${host}${Font_color_suffix}" + fi + echo -e " IPv6\t\t: ${Green_font_prefix}${ipv6}${Font_color_suffix}" + fi + + echo -e " TFO\t\t: ${Green_font_prefix}${tfo}${Font_color_suffix}" + echo -e " DNS\t\t: ${Green_font_prefix}${dns}${Font_color_suffix}" + + if [[ -n "${egress_interface}" ]]; then + echo -e " 出口网卡\t: ${Green_font_prefix}${egress_interface}${Font_color_suffix}" + fi + + if [[ "$ver" == "6" && -n "$dns_ip_pref" ]]; then + echo -e " DNS IP 偏好\t: ${Green_font_prefix}${dns_ip_pref}${Font_color_suffix}" + fi + + if [[ "$ver" == "6" && -n "$mode" ]]; then + echo -e " 混淆模式\t: ${Green_font_prefix}${mode}${Font_color_suffix}" + fi + + echo -e " 版本\t\t: ${Green_font_prefix}${ver}${Font_color_suffix}" + echo -e "——————————————————————————————————————————————————" + if [[ "$ver" == "6" ]]; then + echo -e "${Tip} 如有监听多 IP 多端口需求,请手动编辑配置文件。" + echo -e "——————————————————————————————————————————————————" + fi + echo -e "${Info} Surge 配置:" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + if [[ "${obfs}" == "off" || "${ver}" == "6" ]]; then + if [[ "${ver}" == "6" && -n "${mode}" ]]; then + echo -e "$(uname -n) = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, mode=${mode}, reuse=true, ecn=true" + else + echo -e "$(uname -n) = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true" + fi + else + echo -e "$(uname -n) = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, obfs=${obfs}, obfs-host=${host}, reuse=true, ecn=true" + fi + elif [[ "${ip6}" != "IPv6_Error" ]]; then + if [[ "${obfs}" == "off" || "${ver}" == "6" ]]; then + if [[ "${ver}" == "6" && -n "${mode}" ]]; then + echo -e "$(uname -n) = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, mode=${mode}, reuse=true, ecn=true" + else + echo -e "$(uname -n) = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true" + fi + else + echo -e "$(uname -n) = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, obfs=${obfs}, obfs-host=${host}, reuse=true, ecn=true" + fi + else + echo -e "${Error} 无法获取 IP 地址!" + fi + echo -e "——————————————————————————————————————————————————" + echo -e "${Info} Mihomo / Nikki 配置:" + local mihomo_server="" + if [[ "${ipv4}" != "IPv4_Error" ]]; then + mihomo_server="${ipv4}" + elif [[ "${ip6}" != "IPv6_Error" ]]; then + mihomo_server="${ip6}" + fi + + if [[ -z "${mihomo_server}" ]]; then + echo -e "${Error} 无法获取 IP 地址!" + elif [[ "${ver}" == "6" ]]; then + echo -e "${Tip} mihomo/Nikki 当前文档仅支持 Snell v1/2/3/4/5,暂不输出 v6,v6 请用上面的 Surge 配置。" + else + local mihomo_json="{\"name\":\"$(uname -n)\",\"server\":\"${mihomo_server}\",\"port\":${port},\"psk\":\"${psk}\",\"version\":${ver},\"tfo\":${tfo},\"type\":\"snell\"" + if [[ "${ver}" == "3" || "${ver}" == "4" || "${ver}" == "5" ]]; then + mihomo_json+=",\"udp\":true" + fi + if [[ "${ver}" == "4" || "${ver}" == "5" ]]; then + mihomo_json+=",\"reuse\":true" + fi + if [[ "${obfs}" != "off" && -n "${host}" ]]; then + mihomo_json+=",\"obfs-opts\":{\"mode\":\"${obfs}\",\"host\":\"${host}\"}" + fi + mihomo_json+="}" + echo -e "${mihomo_json}" + fi + echo -e "——————————————————————————————————————————————————" + beforeStartMenu +} + + +# 查看运行状态 +viewStatus(){ + echo -e "${Info} 获取 Snell Server 活动日志 ……" + echo -e "${Tip} 返回主菜单请按 q !" + systemctl status snell-server + startMenu +} + +# 检查地理位置(用于更新脚本源选择) +geo_check() { + api_list="https://blog.cloudflare.com/cdn-cgi/trace https://dash.cloudflare.com/cdn-cgi/trace https://cf-ns.com/cdn-cgi/trace" + ua="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" + set -- $api_list + for url in $api_list; do + text="$(curl -A "$ua" -m 10 -s $url)" + endpoint="$(echo $text | sed -n 's/.*h=\([^ ]*\).*/\1/p')" + if echo $text | grep -qw 'CN'; then + isCN=true + break + elif echo $url | grep -q $endpoint; then + break + fi + done +} + +# 更新脚本 +updateShell(){ + geo_check + if [ ! -z "$isCN" ]; then + shell_url="${SNELL_SCRIPT_CN_URL:-https://mjjtop.com/snell}" + else + shell_url="${SNELL_SCRIPT_URL:-https://mjjtop.com/snell}" + fi + + echo -e "当前版本为 [ ${sh_ver} ],开始检测最新版本..." + sh_new_ver=$(wget --no-check-certificate -qO- "$shell_url"|grep 'sh_ver="'|awk -F "=" '{print $NF}'|sed 's/\"//g'|head -1) + [[ -z ${sh_new_ver} ]] && echo -e "${Error} 检测最新版本失败!" && startMenu + 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 snell.sh --no-check-certificate "$shell_url" && chmod +x snell.sh + echo -e "脚本已更新为最新版本[ ${sh_new_ver} ]!" + echo -e "3s后执行新脚本" + sleep 3s + exec bash snell.sh + else + echo && echo " 已取消..." && echo + sleep 3s + startMenu + fi + else + echo -e "当前已是最新版本[ ${sh_new_ver} ]!" + sleep 3s + startMenu + fi +} + + +# 返回主菜单前提示 +beforeStartMenu() { + echo && echo -n -e "${Yellow_font_prefix}* 按回车返回主菜单 *${Font_color_suffix}" && read temp + startMenu +} + +# 显示版本检查进度 +showVersionCheckProgress(){ + local check_duration=${1:-3} # 检查预期耗时,默认3秒 + echo -e "${Info} 正在检查 Snell 版本信息..." + echo -n "检查进度: " + + # 根据检查时间动态调整进度条 + local steps=30 + local step_time=$(echo "scale=2; $check_duration / $steps" | bc 2>/dev/null || echo "0.1") + + for i in $(seq 1 $steps); do + echo -n -e "${Green_font_prefix}█${Font_color_suffix}" + sleep $step_time + done + echo -e " ${Green_font_prefix}完成${Font_color_suffix}" + echo -e "${Info} 版本检查完成,正在加载主菜单..." + sleep 0.3 +} + +# 检查是否需要进行版本检查 +shouldCheckVersion(){ + local check_interval=3600 # 1小时 = 3600秒 + local last_check_file="/tmp/snell_last_check" + local current_time=$(date +%s) + + # 如果没有安装 Snell,不需要检查 + if [[ ! -e ${snell_bin} || ! -e ${snell_conf} ]]; then + return 1 # 不需要检查 + fi + + # 如果版本不是 Snell v6,旧版本已停更,不需要检查 + local current_ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') + if [[ "$current_ver" != "6" ]]; then + return 1 # 不需要检查 + fi + + # 如果检查记录文件不存在,说明是第一次检查 + if [[ ! -f "$last_check_file" ]]; then + echo "$current_time" > "$last_check_file" + return 0 # 需要检查 + fi + + # 读取上次检查时间 + local last_check_time + last_check_time=$(cat "$last_check_file" 2>/dev/null) + + # 如果文件内容无效,重新记录并检查 + if [[ ! "$last_check_time" =~ ^[0-9]+$ ]]; then + echo "$current_time" > "$last_check_file" + return 0 # 需要检查 + fi + + # 计算时间差 + local time_diff=$((current_time - last_check_time)) + + # 如果超过1小时,需要检查 + if [[ $time_diff -ge $check_interval ]]; then + echo "$current_time" > "$last_check_file" + return 0 # 需要检查 + fi + + return 1 # 不需要检查 +} + +# 检查版本更新(带进度显示) +checkVersionUpdateWithProgress(){ + # 先检查是否需要进行版本检查 + if shouldCheckVersion; then + echo -e "${Info} 正在检查 Snell 版本信息..." + + # 显示绿色进度条 + ( + echo -n "检查进度: " + for i in {1..30}; do + echo -n -e "${Green_font_prefix}█${Font_color_suffix}" + sleep 0.1 + done + echo -e " ${Green_font_prefix}完成${Font_color_suffix}" + ) & + progress_pid=$! + + # 在后台进行版本检查 + updateBuiltinVersions false >/dev/null 2>&1 + checkVersionUpdate false >/dev/null 2>&1 + + # 等待进度条完成 + wait $progress_pid + + echo -e "${Info} 版本检查完成,正在加载主菜单..." + sleep 0.5 + clear + else + # 静默进行版本检查(使用缓存) + if [[ -e ${snell_bin} && -e ${snell_conf} ]]; then + updateBuiltinVersions false >/dev/null 2>&1 + checkVersionUpdate false >/dev/null 2>&1 + fi + fi +} + +# 主菜单 +startMenu(){ + clear + checkRoot + checkSys + sysArch + action=$1 + + # 检查版本更新(在显示菜单前) + checkVersionUpdateWithProgress + + # 检查是否安装了 Snell,需要显示更新或升级选项 + show_update_option=false + major_upgrade_text="" + major_upgrade_func="" + + if [[ -e ${snell_bin} && -e ${snell_conf} ]]; then + current_ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') + if [[ "$current_ver" == "2" ]]; then + major_upgrade_text="Snell v2 更新到 Snell v3" + major_upgrade_func="updateV2toV3" + elif [[ "$current_ver" == "3" ]]; then + major_upgrade_text="Snell v3 更新到 Snell v4" + major_upgrade_func="updateV3toV4" + elif [[ "$current_ver" == "4" ]]; then + major_upgrade_text="Snell v4 更新到 Snell v5" + major_upgrade_func="updateV4toV5" + elif [[ "$current_ver" == "5" ]]; then + major_upgrade_text="Snell v5 更新到 Snell v6" + major_upgrade_func="updateV5toV6" + elif [[ "$current_ver" == "6" ]]; then + show_update_option=true + fi + fi + + echo && echo -e " +============================== +Snell Server 管理脚本 ${Red_font_prefix}[v${sh_ver}]${Font_color_suffix} +============================== + ${Green_font_prefix} 0.${Font_color_suffix} 更新脚本 +—————————————————————————————— + ${Green_font_prefix} 1.${Font_color_suffix} 安装 Snell Server + ${Green_font_prefix} 2.${Font_color_suffix} 卸载 Snell Server" + + # 根据不同情况显示更新选项 + if [[ "$show_update_option" == true ]]; then + if [[ "$update_available" == true ]]; then + echo -e " ${Green_font_prefix} 3.${Font_color_suffix} 更新 Snell Server ${Yellow_font_prefix}(可更新)${Font_color_suffix}" + else + echo -e " ${Green_font_prefix} 3.${Font_color_suffix} 更新 Snell Server" + fi + echo -e "—————————————————————————————— + ${Green_font_prefix} 4.${Font_color_suffix} 启动 Snell Server + ${Green_font_prefix} 5.${Font_color_suffix} 停止 Snell Server + ${Green_font_prefix} 6.${Font_color_suffix} 重启 Snell Server +—————————————————————————————— + ${Green_font_prefix} 7.${Font_color_suffix} 设置 配置信息 + ${Green_font_prefix} 8.${Font_color_suffix} 查看 配置信息 + ${Green_font_prefix} 9.${Font_color_suffix} 查看 运行状态 +—————————————————————————————— + ${Green_font_prefix}00.${Font_color_suffix} 退出脚本" + menu_max=9 + elif [[ -n "$major_upgrade_text" ]]; then + echo -e " ${Green_font_prefix} 3.${Font_color_suffix} ${major_upgrade_text}" + echo -e "—————————————————————————————— + ${Green_font_prefix} 4.${Font_color_suffix} 启动 Snell Server + ${Green_font_prefix} 5.${Font_color_suffix} 停止 Snell Server + ${Green_font_prefix} 6.${Font_color_suffix} 重启 Snell Server +—————————————————————————————— + ${Green_font_prefix} 7.${Font_color_suffix} 设置 配置信息 + ${Green_font_prefix} 8.${Font_color_suffix} 查看 配置信息 + ${Green_font_prefix} 9.${Font_color_suffix} 查看 运行状态 +—————————————————————————————— + ${Green_font_prefix}00.${Font_color_suffix} 退出脚本" + menu_max=9 + else + echo -e "—————————————————————————————— + ${Green_font_prefix} 3.${Font_color_suffix} 启动 Snell Server + ${Green_font_prefix} 4.${Font_color_suffix} 停止 Snell Server + ${Green_font_prefix} 5.${Font_color_suffix} 重启 Snell Server +—————————————————————————————— + ${Green_font_prefix} 6.${Font_color_suffix} 设置 配置信息 + ${Green_font_prefix} 7.${Font_color_suffix} 查看 配置信息 + ${Green_font_prefix} 8.${Font_color_suffix} 查看 运行状态 +—————————————————————————————— + ${Green_font_prefix}00.${Font_color_suffix} 退出脚本" + menu_max=8 + fi + + echo "==============================" && echo + if [[ -e ${snell_bin} ]]; then + checkStatus + if [[ "$status" == "running" ]]; then + echo -e " 当前状态: ${Green_font_prefix}已安装${Yellow_font_prefix}[v$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}')]${Font_color_suffix}且${Green_font_prefix}已启动${Font_color_suffix}" + else + echo -e " 当前状态: ${Green_font_prefix}已安装${Yellow_font_prefix}[v$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}')]${Font_color_suffix}但${Red_font_prefix}未启动${Font_color_suffix}" + fi + else + echo -e " 当前状态: ${Red_font_prefix}未安装${Font_color_suffix}" + fi + echo + + if [[ "$show_update_option" == true || -n "$major_upgrade_text" ]]; then + read -e -p " 请输入数字[0-9]:" num + else + read -e -p " 请输入数字[0-8]:" num + fi + + # 根据不同菜单模式处理用户输入 + if [[ "$show_update_option" == true ]]; then + case "$num" in + 0) updateShell ;; + 1) installSnell ;; + 2) uninstallSnell ;; + 3) updateSnellServer ;; + 4) startSnell ;; + 5) stopSnell ;; + 6) restartSnell ;; + 7) setConfig ;; + 8) viewConfig ;; + 9) viewStatus ;; + 00) exit 1 ;; + *) echo -e "请输入正确数字${Yellow_font_prefix}[0-9]${Font_color_suffix}"; sleep 2s; startMenu ;; + esac + elif [[ -n "$major_upgrade_func" ]]; then + case "$num" in + 0) updateShell ;; + 1) installSnell ;; + 2) uninstallSnell ;; + 3) ${major_upgrade_func} ;; + 4) startSnell ;; + 5) stopSnell ;; + 6) restartSnell ;; + 7) setConfig ;; + 8) viewConfig ;; + 9) viewStatus ;; + 00) exit 1 ;; + *) echo -e "请输入正确数字${Yellow_font_prefix}[0-9]${Font_color_suffix}"; sleep 2s; startMenu ;; + esac + else + case "$num" in + 0) updateShell ;; + 1) installSnell ;; + 2) uninstallSnell ;; + 3) startSnell ;; + 4) stopSnell ;; + 5) restartSnell ;; + 6) setConfig ;; + 7) viewConfig ;; + 8) viewStatus ;; + 00) exit 1 ;; + *) echo -e "请输入正确数字${Yellow_font_prefix}[0-8]${Font_color_suffix}"; sleep 2s; startMenu ;; + esac + fi + +} + +startMenu