Files
proxy-installer/scripts/snell.sh
2026-07-05 16:14:40 +08:00

2608 lines
91 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
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 </dev/urandom | head -c 20)
echo -e "${Info} 已自动生成新密钥: ${Green_font_prefix}${psk}${Font_color_suffix}"
fi
fi
}
# 写入配置文件
writeConfig(){
local config_content="[snell-server]
listen = ${listen_val}
$(if [[ ${ver} != "6" ]]; then echo "ipv6 = ${ipv6}"; else echo "# ipv6 = ${ipv6}"; fi)
psk = ${psk}
$(if [[ ${ver} != "6" ]]; then echo "obfs = ${obfs}"; else echo "# obfs = ${obfs}"; fi)"
if [[ ${ver} != "6" && ${obfs} != "off" ]]; then
config_content+=$'\n'"obfs-host = ${host}"
elif [[ ${ver} == "6" && ${obfs} != "off" ]]; then
config_content+=$'\n'"# obfs-host = ${host}"
fi
config_content+=$'\n'"tfo = ${tfo}
dns = ${dns}"
if [[ ${ver} == "6" ]]; then
if [[ -n "${dns_ip_pref}" ]]; then
config_content+=$'\n'"dns-ip-preference = ${dns_ip_pref}"
else
config_content+=$'\n'"dns-ip-preference = default"
fi
if [[ -n "${mode}" ]]; then
config_content+=$'\n'"mode = ${mode}"
else
config_content+=$'\n'"mode = default"
fi
else
if [[ -n "${dns_ip_pref}" ]]; then
config_content+=$'\n'"# dns-ip-preference = ${dns_ip_pref}"
fi
if [[ -n "${mode}" ]]; then
config_content+=$'\n'"# mode = ${mode}"
fi
fi
if [[ -n "${egress_interface}" ]]; then
config_content+=$'\n'"egress-interface = ${egress_interface}"
fi
config_content+=$'\n'"version = ${ver}"
# 保留未知的自定义配置项
if [[ -f "${snell_conf}" ]]; then
local custom_configs=$(awk -F '=' '{
if ($1 !~ /^[[:space:]]*(listen|ipv6|psk|obfs|obfs-host|tfo|dns|dns-ip-preference|mode|version|egress-interface|\[snell-server\]|#)[[:space:]]*/) {
if (NF > 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/urandom | head -c 20)
break
else
if [[ ${#input_psk} -ge 16 ]] && [[ ${#input_psk} -le 255 ]]; then
psk=$input_psk
break
else
echo -e "${Error} Snell v6 密钥长度必须在 16 到 255 位之间,请重新输入!"
fi
fi
done
else
echo -e -n "${p_prompt}"
read -e input_psk
if [[ -z "${input_psk}" ]]; then
psk=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 16)
else
psk=$input_psk
fi
fi
echo && echo "=============================="
echo -e "密钥 : ${Red_background_prefix} ${psk} ${Font_color_suffix}"
echo "==============================" && echo
}
# 设置 OBFS
setObfs(){
echo -e "配置 OBFS${Tip} 无特殊作用不建议启用该项。
==================================
${Green_font_prefix} 1.${Font_color_suffix} TLS ${Green_font_prefix} 2.${Font_color_suffix} HTTP ${Green_font_prefix} 3.${Font_color_suffix} 关闭
=================================="
local current_opt="3"
if [[ "$obfs" == "tls" ]]; then current_opt="1"; elif [[ "$obfs" == "http" ]]; then current_opt="2"; fi
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}3.关闭)"
[[ -n "$obfs" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_opt}.${obfs} | ${Green_font_prefix}默认${Font_color_suffix}3.关闭)"
echo -e -n "${p_prompt}"
read -e input_obfs
[[ -z "${input_obfs}" ]] && input_obfs="3"
obfs=$input_obfs
if [[ ${obfs} == "1" ]]; then
obfs="tls"
setHost # 强制设置 OBFS 域名
elif [[ ${obfs} == "2" ]]; then
obfs="http"
setHost # 强制设置 OBFS 域名
elif [[ ${obfs} == "3" ]]; then
obfs="off"
host="" # 清空 host
else
obfs="off"
host="" # 清空 host
fi
echo && echo "=================================="
echo -e "OBFS 状态:${Red_background_prefix} ${obfs} ${Font_color_suffix}"
if [[ ${obfs} != "off" ]]; then
echo -e "OBFS 域名:${Red_background_prefix} ${host} ${Font_color_suffix}"
fi
echo "==================================" && echo
}
# 设置协议版本
setVer(){
echo -e "配置 Snell Server 协议版本${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
=================================="
local current_ver_display=""
[[ -n "$ver" ]] && current_ver_display="${ver}.v${ver}"
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}4.v4)"
[[ -n "$ver" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_ver_display} | ${Green_font_prefix}默认${Font_color_suffix}4.v4)"
echo -e -n "${p_prompt}"
read -e input_ver
[[ -z "${input_ver}" ]] && input_ver="4"
ver=$input_ver
if [[ ${ver} == "2" ]]; then
ver=2
elif [[ ${ver} == "3" ]]; then
ver=3
elif [[ ${ver} == "4" ]]; then
ver=4
elif [[ ${ver} == "5" ]]; then
ver=5
elif [[ ${ver} == "6" ]]; then
ver=6
else
ver=4
fi
echo && echo "=================================="
echo -e "Snell Server 协议版本:${Red_background_prefix} ${ver} ${Font_color_suffix}"
echo "==================================" && echo
}
# 设置 OBFS 域名
setHost(){
echo "请输入 Snell Server 域名Snell v4 版本及以上如无特别需求可忽略。"
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}: www.wechat.com):"
[[ -n "$host" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${host} | ${Green_font_prefix}默认${Font_color_suffix}: www.wechat.com):"
echo -e -n "${p_prompt}"
read -e input_host
[[ -z "${input_host}" ]] && input_host="www.wechat.com"
host=$input_host
echo && echo "=============================="
echo -e "域名 : ${Red_background_prefix} ${host} ${Font_color_suffix}"
echo "==============================" && echo
}
# 设置 TCP Fast Open
setTFO(){
echo -e "是否开启 TCP Fast Open
==================================
${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭
=================================="
local current_opt="1"
if [[ "$tfo" == "false" ]]; then current_opt="2"; fi
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}1.开启)"
[[ -n "$tfo" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_opt}.${tfo} | ${Green_font_prefix}默认${Font_color_suffix}1.开启)"
echo -e -n "${p_prompt}"
read -e input_tfo
[[ -z "${input_tfo}" ]] && input_tfo="1"
tfo=$input_tfo
if [[ ${tfo} == "1" ]]; then
tfo=true
enableTCPFastOpen
else
tfo=false
fi
echo && echo "=================================="
echo -e "TCP Fast Open 开启状态:${Red_background_prefix} ${tfo} ${Font_color_suffix}"
echo "==================================" && echo
}
# 设置 DNS
setDNS(){
echo -e "${Tip} 请输入正确格式的 DNS多条记录以英文逗号隔开仅支持Snell v4.1.0b1 版本及以上。"
local p_prompt="(${Green_font_prefix}默认值${Font_color_suffix}1.1.1.1, 8.8.8.8, 2001:4860:4860::8888)"
[[ -n "$dns" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${dns} | ${Green_font_prefix}默认值${Font_color_suffix}1.1.1.1, 8.8.8.8, 2001:4860:4860::8888)"
echo -e -n "${p_prompt}"
read -e input_dns
[[ -z "${input_dns}" ]] && input_dns="1.1.1.1, 8.8.8.8, 2001:4860:4860::8888"
dns=$input_dns
echo && echo "=================================="
echo -e "当前 DNS 为:${Red_background_prefix} ${dns} ${Font_color_suffix}"
echo "==================================" && echo
}
# 设置 DNS IP 偏好 (Snell v6 专属)
setDNSIPPref(){
echo -e "配置 DNS IP 偏好 (Snell v6 专属)
==================================
${Green_font_prefix} 1.${Font_color_suffix} default ${Green_font_prefix} 2.${Font_color_suffix} prefer-ipv4 ${Green_font_prefix} 3.${Font_color_suffix} prefer-ipv6 ${Green_font_prefix} 4.${Font_color_suffix} ipv4-only ${Green_font_prefix} 5.${Font_color_suffix} ipv6-only
=================================="
local current_opt="1"
if [[ "$dns_ip_pref" == "prefer-ipv4" ]]; then current_opt="2"; elif [[ "$dns_ip_pref" == "prefer-ipv6" ]]; then current_opt="3"; elif [[ "$dns_ip_pref" == "ipv4-only" ]]; then current_opt="4"; elif [[ "$dns_ip_pref" == "ipv6-only" ]]; then current_opt="5"; fi
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}1.default)"
[[ -n "$dns_ip_pref" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_opt}.${dns_ip_pref} | ${Green_font_prefix}默认${Font_color_suffix}1.default)"
echo -e -n "${p_prompt}"
read -e input_pref
[[ -z "${input_pref}" && -n "$dns_ip_pref" ]] && input_pref=$current_opt
[[ -z "${input_pref}" && -z "$dns_ip_pref" ]] && input_pref="1"
dns_ip_pref_opt=$input_pref
if [[ ${dns_ip_pref_opt} == "2" ]]; then
dns_ip_pref="prefer-ipv4"
elif [[ ${dns_ip_pref_opt} == "3" ]]; then
dns_ip_pref="prefer-ipv6"
elif [[ ${dns_ip_pref_opt} == "4" ]]; then
dns_ip_pref="ipv4-only"
elif [[ ${dns_ip_pref_opt} == "5" ]]; then
dns_ip_pref="ipv6-only"
else
dns_ip_pref="default"
fi
echo && echo "=================================="
echo -e "DNS IP 偏好 状态:${Red_background_prefix} ${dns_ip_pref} ${Font_color_suffix}"
echo "==================================" && echo
}
# 设置 模式 (Snell v6 专属)
setMode(){
echo -e "配置 混淆模式
==================================
${Green_font_prefix} 1.${Font_color_suffix} default ${Green_font_prefix} 2.${Font_color_suffix} unshaped ${Green_font_prefix} 3.${Font_color_suffix} unsafe-raw
=================================="
local current_opt="1"
if [[ "$mode" == "unshaped" ]]; then current_opt="2"; elif [[ "$mode" == "unsafe-raw" ]]; then current_opt="3"; fi
local p_prompt="(${Green_font_prefix}默认${Font_color_suffix}1.default)"
[[ -n "$mode" ]] && p_prompt="(${Yellow_font_prefix}当前${Font_color_suffix}: ${current_opt}.${mode} | ${Green_font_prefix}默认${Font_color_suffix}1.default)"
echo -e -n "${p_prompt}"
read -e input_pref
[[ -z "${input_pref}" && -n "$mode" ]] && input_pref=$current_opt
[[ -z "${input_pref}" && -z "$mode" ]] && input_pref="1"
mode_opt=$input_pref
if [[ ${mode_opt} == "2" ]]; then
mode="unshaped"
elif [[ ${mode_opt} == "3" ]]; then
mode="unsafe-raw"
else
mode="default"
fi
echo && echo "=================================="
echo -e "混淆模式:${Red_background_prefix} ${mode} ${Font_color_suffix}"
echo "==================================" && echo
}
# 切换二进制版本
switchBinary(){
local old_ver=$1
local new_ver=$2
echo -e "${Info} 开始下载 Snell v${new_ver} 版本..."
systemctl stop snell-server
if [[ -e "${snell_bin}" ]]; then
echo -e "${Info} 备份当前程序文件..."
cp "${snell_bin}" "${snell_bin}.v${old_ver}.backup.$(date +%Y%m%d_%H%M%S)"
fi
local target_full_version=""
local web_version=$(getLatestVersionFromWeb "v${new_ver}")
local script_version=""
case "$new_ver" in
"2") script_version=${snell_v2_version} ;;
"3") script_version=${snell_v3_version} ;;
"4") script_version=${snell_v4_version} ;;
"5") script_version=${snell_v5_version} ;;
"6") script_version=${snell_v6_version} ;;
esac
if [[ -n "$web_version" ]]; then
if validateVersionUrl "$web_version"; then
target_full_version="$web_version"
echo -e "${Info} 使用网页获取的 Snell v${new_ver} 版本: v${target_full_version}"
fi
fi
if [[ -z "$target_full_version" && -n "$script_version" ]]; then
if validateVersionUrl "$script_version"; then
target_full_version="$script_version"
echo -e "${Info} 使用脚本内置的 Snell v${new_ver} 版本: v${target_full_version}"
fi
fi
if [[ -z "$target_full_version" ]]; then
target_full_version="$script_version"
fi
if [[ -n "$target_full_version" ]]; then
downloadSnell "${target_full_version}" "Snell v${new_ver} 版本" true "$old_ver"
if [[ $? -eq 0 ]]; then
echo -e "${Info} Snell v${new_ver} 核心切换成功!"
return 0
else
echo -e "${Error} Snell v${new_ver} 下载失败,正在回滚..."
fi
else
echo -e "${Error} 无法找到 Snell v${new_ver} 的下载链接,切换失败!"
fi
backup_file=$(ls -t "${snell_bin}".v${old_ver}.backup.* 2>/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}"
local node_name_default="$(uname -n)"
local node_name=""
echo -e -n "请输入节点名称(${Green_font_prefix}默认${Font_color_suffix}: ${node_name_default}):"
read -e node_name
[[ -z "${node_name}" ]] && node_name="${node_name_default}"
echo -e " 节点名称 : ${Green_font_prefix}${node_name}${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 "${node_name} = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, mode=${mode}, reuse=true, ecn=true"
else
echo -e "${node_name} = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true"
fi
else
echo -e "${node_name} = 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 "${node_name} = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, mode=${mode}, reuse=true, ecn=true"
else
echo -e "${node_name} = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true"
fi
else
echo -e "${node_name} = 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暂不输出 v6v6 请用上面的 Surge 配置。"
else
local mihomo_json="{\"name\":\"${node_name}\",\"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