Repository: xOS/Snell Branch: master Commit: 88fe08dfb6d2 Files: 5 Total size: 57.2 KB Directory structure: gitextract_62vk9sbw/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── sync.py │ └── sync.yaml ├── README.md └── Snell.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [xOS]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: ['https://donate.stripe.com/28obMffW78Pogb6145','https://ifdian.net/a/herby'] ================================================ FILE: .github/workflows/sync.py ================================================ import os import time import requests import hashlib from github import Github def get_github_latest_release(): g = Github() repo = g.get_repo("xOS/Snell") release = repo.get_latest_release() if release: print(f"Latest release tag is: {release.tag_name}") print(f"Latest release info is: {release.body}") files = [] for asset in release.get_assets(): url = asset.browser_download_url name = asset.name response = requests.get(url) if response.status_code == 200: with open(name, 'wb') as f: f.write(response.content) print(f"Downloaded {name}") else: print(f"Failed to download {name}") file_abs_path = get_abs_path(asset.name) files.append(file_abs_path) print('Checking file integrities') verify_checksum(get_abs_path("checksums.txt")) sync_to_gitee(release.tag_name, release.body, files) else: print("No releases found.") def delete_gitee_releases(latest_id, client, uri, token): get_data = { 'access_token': token } release_info = [] release_response = client.get(uri, json=get_data) if release_response.status_code == 200: release_info = release_response.json() else: print( f"Request failed with status code {release_response.status_code}") release_ids = [] for block in release_info: if 'id' in block: release_ids.append(block['id']) print(f'Current release ids: {release_ids}') release_ids.remove(latest_id) for id in release_ids: release_uri = f"{uri}/{id}" delete_data = { 'access_token': token } delete_response = client.delete(release_uri, json=delete_data) if delete_response.status_code == 204: print(f'Successfully deleted release #{id}.') else: raise ValueError( f"Request failed with status code {delete_response.status_code}") def sync_to_gitee(tag: str, body: str, files: slice): release_id = "" owner = "Ten" repo = "ServerAgent" release_api_uri = f"https://gitee.com/api/v5/repos/{owner}/{repo}/releases" api_client = requests.Session() api_client.headers.update({ 'Accept': 'application/json', 'Content-Type': 'application/json' }) access_token = os.environ['GITEE_TOKEN'] release_data = { 'access_token': access_token, 'tag_name': tag, 'name': tag, 'body': body, 'prerelease': False, 'target_commitish': 'master' } while True: try: release_api_response = api_client.post( release_api_uri, json=release_data, timeout=30) release_api_response.raise_for_status() break except requests.exceptions.Timeout as errt: print(f"Request timed out: {errt} Retrying in 60 seconds...") time.sleep(60) except requests.exceptions.RequestException as err: print(f"Request failed: {err}") break if release_api_response.status_code == 201: release_info = release_api_response.json() release_id = release_info.get('id') else: print( f"Request failed with status code {release_api_response.status_code}") print(f"Gitee release id: {release_id}") asset_api_uri = f"{release_api_uri}/{release_id}/attach_files" for file_path in files: success = False while not success: files = { 'file': open(file_path, 'rb') } asset_api_response = requests.post( asset_api_uri, params={'access_token': access_token}, files=files) if asset_api_response.status_code == 201: asset_info = asset_api_response.json() asset_name = asset_info.get('name') print(f"Successfully uploaded {asset_name}!") success = True else: print( f"Request failed with status code {asset_api_response.status_code}") # 仅保留最新 Release 以防超出 Gitee 仓库配额 try: delete_gitee_releases(release_id, api_client, release_api_uri, access_token) except ValueError as e: print(e) api_client.close() print("Sync is completed!") def get_abs_path(path: str): wd = os.getcwd() return os.path.join(wd, path) def compute_sha256(file: str): sha256_hash = hashlib.sha256() buf_size = 65536 with open(file, 'rb') as f: while True: data = f.read(buf_size) if not data: break sha256_hash.update(data) return sha256_hash.hexdigest() def verify_checksum(checksum_file: str): with open(checksum_file, 'r') as f: lines = f.readlines() for line in lines: checksum, file = line.strip().split() abs_path = get_abs_path(file) computed_hash = compute_sha256(abs_path) if checksum == computed_hash: print(f"{file}: OK") else: print(f"{file}: FAIL (expected {checksum}, got {computed_hash})") print("Will run the download process again") get_github_latest_release() break get_github_latest_release() ================================================ FILE: .github/workflows/sync.yaml ================================================ name: Sync Code to Gitee on: workflow_dispatch: push: branches: [master] jobs: sync-code-to-gitee: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: adambirds/sync-github-to-gitlab-action@v1.1.0 with: destination_repository: git@gitee.com:Ten/Snell.git destination_branch_name: master destination_ssh_key: ${{ secrets.GITEE_SSH_KEY }} ================================================ FILE: README.md ================================================ # Snell 管理脚本 > 纯自用。 ## 默认 ```bash wget -O snell.sh --no-check-certificate https://git.io/Snell.sh && chmod +x snell.sh && ./snell.sh ``` ## 国内 ```bash wget -O snell.sh --no-check-certificate https://gitee.com/ten/Snell/raw/master/Snell.sh && chmod +x snell.sh && ./snell.sh ``` ## 注意 * 请手动放行防火墙相应端口。 ## Star 历史 [![Star History Chart](https://api.star-history.com/svg?repos=xOS/Snell&type=Date)](https://www.star-history.com/#xOS/Snell&Date) ================================================ FILE: Snell.sh ================================================ #!/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.8.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" 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 jq -y else apt-get update apt-get install gzip wget curl unzip jq -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}') # v2 和 v3 不支持版本检查,直接返回 if [[ "$current_ver" == "2" || "$current_ver" == "3" ]]; then update_available=false return 0 fi if [[ -e ${snell_version_file} ]]; then installed_version=$(cat ${snell_version_file} | sed 's/^v//') 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") ;; *) 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} 网页版本 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} 发现更新:当前版本 v${installed_version} -> 最新版本 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(备用源) # 下载并安装 Snell v2(GitHub 备份源) downloadSnellV2() { downloadSnellFromGitHub "${snell_v2_version}" "v2 GitHub备份源版" } # 下载并安装 Snell v3(GitHub 备份源) downloadSnellV3() { downloadSnellFromGitHub "${snell_v3_version}" "v3 GitHub备份源版" } # 通用下载并安装 Snell 函数(GitHub 备份源) downloadSnellFromGitHub(){ local version=$1 local version_type=$2 echo -e "${Info} 试图请求 ${Yellow_font_prefix}${version_type}${Font_color_suffix} Snell Server ……" local backup_url="https://raw.githubusercontent.com/xOS/Others/master/snell/v${version}/snell-server-v${version}-linux-${arch}.zip" 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 } # 下载并安装 Snell v4(官方源) downloadSnellV4(){ downloadSnell "${snell_v4_version}" "v4 官网源版" } # 下载并安装 Snell v5(官方源) downloadSnellV5(){ downloadSnell "${snell_v5_version}" "v5 官网源版" } # 通用下载并安装 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-5]${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 ==================================" 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 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 服务配置完成!" } # 写入配置文件 writeConfig(){ if [[ -f "${snell_conf}" ]]; then cp "${snell_conf}" "${snell_conf}.bak.$(date +%Y%m%d_%H%M%S)" echo -e "${Info} 已备份旧配置文件到 ${snell_conf}.bak" fi cat > "${snell_conf}" << EOF [snell-server] listen = ::0:${port} ipv6 = ${ipv6} psk = ${psk} obfs = ${obfs} $(if [[ ${obfs} != "off" ]]; then echo "obfs-host = ${host}"; fi) tfo = ${tfo} dns = ${dns} version = ${ver} EOF } # 读取配置文件 readConfig(){ [[ ! -e ${snell_conf} ]] && echo -e "${Error} Snell Server 配置文件不存在!" && exit 1 ipv6=$(cat ${snell_conf}|grep 'ipv6 = '|awk -F 'ipv6 = ' '{print $NF}') port=$(grep -E '^listen\s*=' ${snell_conf} | awk -F ':' '{print $NF}' | xargs) 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}') } # 设置端口 setPort(){ while true; do echo -e "${Tip} 本步骤不涉及系统防火墙端口操作,请手动放行相应端口!" echo -e "请输入 Snell Server 端口${Yellow_font_prefix}[1-65535]${Font_color_suffix}" read -e -p "(默认: 2345):" port [[ -z "${port}" ]] && port="2345" if [[ $port =~ ^[0-9]+$ ]] && [[ $port -ge 1 && $port -le 65535 ]]; then if 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} 关闭 ==================================" read -e -p "(默认:2.关闭):" ipv6 [[ -z "${ipv6}" ]] && ipv6="false" if [[ ${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] " read -e -p "(默认: 随机生成):" psk [[ -z "${psk}" ]] && 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${current_v4_version}" > ${snell_version_file} sed -i "s/version = 5/version = 4/g" "${snell_conf}" systemctl start snell-server echo -e "${Info} 已回滚到 v4 版本" fi fi else echo -e "${Error} v5 下载失败,保持 v4 版本" 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 "4") downloadSnell "${latest_available_version}" "v4 最新版" true "${current_installed_version}" ;; "5") downloadSnell "${latest_available_version}" "v5 最新版" true "${current_installed_version}" ;; *) echo -e "${Error} 不支持的版本: 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 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) # v2 和 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) if [[ -n "$cached_v4" && -n "$cached_v5" ]]; 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 return 0 fi fi fi [[ "$show_info" == true ]] && echo -e "${Info} 正在检查官方最新版本..." # 获取最新的 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 # 获取最新的 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 # 更新缓存(只缓存 v4 和 v5) echo "$current_time" > "$cache_file" echo "$latest_v4_web" >> "$cache_file" echo "$latest_v5_web" >> "$cache_file" } # 强制检查最新版本(清除缓存) forceCheckVersions(){ echo -e "${Info} 强制检查 Snell 最新版本..." rm -f "/tmp/snell_version_cache" updateBuiltinVersions true echo -e "${Info} 版本检查完成!" echo -e "${Info} 脚本内置 v4 版本: ${Green_font_prefix}${snell_v4_version}${Font_color_suffix}" echo -e "${Info} 脚本内置 v5 版本: ${Green_font_prefix}${snell_v5_version}${Font_color_suffix}" # 获取网页版本进行对比 web_v4=$(getLatestVersionFromWeb "v4") web_v5=$(getLatestVersionFromWeb "v5") if [[ -n "$web_v4" ]]; then echo -e "${Info} 网页获取 v4 版本: ${Yellow_font_prefix}${web_v4}${Font_color_suffix}" compareVersions "${snell_v4_version}" "$web_v4" case $? in 1) echo -e "${Info} v4 版本状态: 脚本内置版本与网页版本相同" ;; 0) echo -e "${Info} v4 版本状态: 脚本内置版本比网页版本更新" ;; 2) echo -e "${Tip} v4 版本状态: 网页版本比脚本内置版本更新" ;; esac fi if [[ -n "$web_v5" ]]; then echo -e "${Info} 网页获取 v5 版本: ${Yellow_font_prefix}${web_v5}${Font_color_suffix}" compareVersions "${snell_v5_version}" "$web_v5" case $? in 1) echo -e "${Info} v5 版本状态: 脚本内置版本与网页版本相同" ;; 0) echo -e "${Info} v5 版本状态: 脚本内置版本比网页版本更新" ;; 2) echo -e "${Tip} v5 版本状态: 网页版本比脚本内置版本更新" ;; 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 echo -e "${Info} 移除网络优化配置..." rm -f "${sysctl_conf}" echo -e "${Info} 配置文件保留在 ${snell_conf},如需完全删除请手动执行: 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}" echo -e " OBFS\t\t: ${Green_font_prefix}${obfs}${Font_color_suffix}" echo -e " 域名\t\t: ${Green_font_prefix}${host}${Font_color_suffix}" echo -e " IPv6\t\t: ${Green_font_prefix}${ipv6}${Font_color_suffix}" echo -e " TFO\t\t: ${Green_font_prefix}${tfo}${Font_color_suffix}" echo -e " DNS\t\t: ${Green_font_prefix}${dns}${Font_color_suffix}" echo -e " 版本\t\t: ${Green_font_prefix}${ver}${Font_color_suffix}" echo -e "—————————————————————————" echo -e "${Info} Surge 配置:" if [[ "${ipv4}" != "IPv4_Error" ]]; then if [[ "${obfs}" == "off" ]]; then echo -e "$(uname -n) = snell, ${ipv4}, ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true" 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" ]]; then echo -e "$(uname -n) = snell, [${ip6}], ${port}, psk=${psk}, version=${ver}, tfo=${tfo}, reuse=true, ecn=true" 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 "—————————————————————————" 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="https://gitee.com/ten/Snell/raw/master/Snell.sh" else shell_url="https://raw.githubusercontent.com/xOS/Snell/master/Snell.sh" 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 # 如果检查记录文件不存在,说明是第一次检查 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 # 检查是否安装了 v4 版本,需要显示 v4 到 v5 更新选项 show_v4_to_v5_option=false show_update_option=false if [[ -e ${snell_bin} && -e ${snell_conf} ]]; then current_ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') if [[ "$current_ver" == "4" ]]; then show_v4_to_v5_option=true fi # 只有 v4 和 v5 显示更新选项,v2 和 v3 不显示 if [[ "$current_ver" == "4" || "$current_ver" == "5" ]]; 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_v4_to_v5_option" == true ]]; then # v4 版本,同时显示两个更新选项 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} v4 更新到 v5 —————————————————————————————— ${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} 重启 Snell Server —————————————————————————————— ${Green_font_prefix} 8.${Font_color_suffix} 设置 配置信息 ${Green_font_prefix} 9.${Font_color_suffix} 查看 配置信息 ${Green_font_prefix}10.${Font_color_suffix} 查看 运行状态 —————————————————————————————— ${Green_font_prefix}00.${Font_color_suffix} 退出脚本" menu_max=10 elif [[ "$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 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_v4_to_v5_option" == true ]]; then read -e -p " 请输入数字[0-10]:" num elif [[ "$show_update_option" == true ]]; then read -e -p " 请输入数字[0-9]:" num else read -e -p " 请输入数字[0-8]:" num fi # 根据不同菜单模式处理用户输入 if [[ "$show_v4_to_v5_option" == true ]]; then case "$num" in 0) updateShell ;; 1) installSnell ;; 2) uninstallSnell ;; 3) updateSnellServer ;; 4) updateV4toV5 ;; 5) startSnell ;; 6) stopSnell ;; 7) restartSnell ;; 8) setConfig ;; 9) viewConfig ;; 10) viewStatus ;; 00) exit 1 ;; *) echo -e "请输入正确数字${Yellow_font_prefix}[0-10]${Font_color_suffix}" sleep 2s startMenu ;; esac elif [[ "$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 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