Full Code of xOS/Snell for AI

master 88fe08dfb6d2 cached
5 files
57.2 KB
18.3k tokens
6 symbols
1 requests
Download .txt
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/urandom | head -c 16)
	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} 关闭
=================================="
    read -e -p "(默认:3.关闭):" obfs
    [[ -z "${obfs}" ]] && obfs="3"
    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-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
		ver=2
	elif [[ ${ver} == "3" ]]; then
		ver=3
	elif [[ ${ver} == "4" ]]; then
		ver=4
	elif [[ ${ver} == "5" ]]; then
		ver=5
	else
		ver=4
	fi
	echo && echo "=================================="
	echo -e "Snell Server 协议版本:${Red_background_prefix} ${ver} ${Font_color_suffix}"
	echo "==================================" && echo
}

# 设置 OBFS 域名
setHost(){
	echo "请输入 Snell Server 域名,v4 版本及以上如无特别需求可忽略。"
	read -e -p "(默认: icloud.com):" host
	[[ -z "${host}" ]] && host=icloud.com
	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} 关闭
=================================="
	read -e -p "(默认:1.开启):" tfo
	[[ -z "${tfo}" ]] && tfo="1"
	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,多条记录以英文逗号隔开,仅支持 v4.1.0b1 版本及以上。"
	read -e -p "(默认值:1.1.1.1, 8.8.8.8, 2001:4860:4860::8888):" dns
	[[ -z "${dns}" ]] && dns="1.1.1.1, 8.8.8.8, 2001:4860:4860::8888"
	echo && echo "=================================="
	echo -e "当前 DNS 为:${Red_background_prefix} ${dns} ${Font_color_suffix}"
	echo "==================================" && echo
}

# 修改配置
setConfig(){
    checkInstalledStatus
    echo && echo -e "请输入要操作配置项的序号,然后回车
==============================
 ${Green_font_prefix}1.${Font_color_suffix} 修改 端口
 ${Green_font_prefix}2.${Font_color_suffix} 修改 密钥
 ${Green_font_prefix}3.${Font_color_suffix} 配置 OBFS
 ${Green_font_prefix}4.${Font_color_suffix} 配置 OBFS 域名
 ${Green_font_prefix}5.${Font_color_suffix} 开关 IPv6 解析
 ${Green_font_prefix}6.${Font_color_suffix} 开关 TCP Fast Open
 ${Green_font_prefix}7.${Font_color_suffix} 配置 DNS
 ${Green_font_prefix}8.${Font_color_suffix} 配置 Snell Server 协议版本
==============================
 ${Green_font_prefix}9.${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
        setVer
        writeConfig
        restartSnell
    elif [[ "${modify}" == "9" ]]; then
        setPort
        setPSK
        setObfs  # 在 setObfs 中已处理 host
        setIpv6
        setTFO
        setDNS
        setVer
        writeConfig
        restartSnell
    else
        echo -e "${Error} 请输入正确数字${Yellow_font_prefix}[1-9]${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
startSnell(){
    checkInstalledStatus
    checkStatus
    if [[ "$status" == "running" ]]; then
        echo -e "${Info} Snell Server 已在运行!"
    else
        echo -e "${Info} 正在启动 Snell Server..."
        systemctl start snell-server
        
        # 等待启动完成,最多等待5秒
        local timeout=5
        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
}

# v4 更新到 v5
updateV4toV5(){
	checkInstalledStatus
	readConfig
	
	# 检查当前版本是否为 v4
	if [[ "$ver" != "4" ]]; then
		echo -e "${Error} 当前版本不是 v4,无法使用此功能!当前版本:v${ver}"
		sleep 3s
		startMenu
		return 1
	fi
	
	echo -e "${Info} 即将将 Snell Server 从 v4 更新到 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 Server v4 到 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
	
	# 获取当前安装的 v4 版本作为回退版本
	current_v4_version=$(cat ${snell_version_file} | sed 's/^v//')
	
	# 获取最新的 v5 版本号(优先使用网页版本,然后是脚本内置版本)
	web_v5_version=$(getLatestVersionFromWeb "v5")
	script_v5_version="${snell_v5_version}"
	
	# 选择最新的 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} 使用网页获取的 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} 使用脚本内置的 v5 版本: v${target_v5_version}"
		fi
	fi
	
	# 如果都无效,取消更新
	if [[ -z "$target_v5_version" ]]; then
		echo -e "${Error} 无法找到有效的 v5 版本进行更新"
		systemctl start snell-server
		sleep 3s
		startMenu
		return 1
	fi
	
	# 下载并安装 v5,启用回退机制
	echo -e "${Info} 开始下载 v5 版本..."
	downloadSnell "${target_v5_version}" "v5 版本" true "${current_v4_version}"
	
	if [[ $? -eq 0 ]]; then
		# 更新配置文件中的版本号
		echo -e "${Info} 更新配置文件版本号..."
		sed -i "s/version = 4/version = 5/g" "${snell_conf}"
		
		# 重新加载 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} v4 到 v5 更新成功!"
			echo -e "${Info} 当前版本:v${actual_version}"
			
			# 如果实际版本是 v4(说明回退了),更新配置文件版本号
			if [[ "$actual_version" =~ ^4\. ]]; then
				sed -i "s/version = 5/version = 4/g" "${snell_conf}"
				echo -e "${Tip} 注意:由于下载链接问题,已回退到 v4 版本"
			fi
		else
			echo -e "${Error} 服务启动失败,正在回滚..."
			# 回滚到 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} 已回滚到 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
Download .txt
gitextract_62vk9sbw/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── sync.py
│       └── sync.yaml
├── README.md
└── Snell.sh
Download .txt
SYMBOL INDEX (6 symbols across 1 files)

FILE: .github/workflows/sync.py
  function get_github_latest_release (line 8) | def get_github_latest_release():
  function delete_gitee_releases (line 36) | def delete_gitee_releases(latest_id, client, uri, token):
  function sync_to_gitee (line 70) | def sync_to_gitee(tag: str, body: str, files: slice):
  function get_abs_path (line 143) | def get_abs_path(path: str):
  function compute_sha256 (line 148) | def compute_sha256(file: str):
  function verify_checksum (line 160) | def verify_checksum(checksum_file: str):
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (70K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 814,
    "preview": "# These are supported funding model platforms\n\ngithub: [xOS]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g"
  },
  {
    "path": ".github/workflows/sync.py",
    "chars": 5447,
    "preview": "import os\nimport time\nimport requests\nimport hashlib\nfrom github import Github\n\n\ndef get_github_latest_release():\n    g "
  },
  {
    "path": ".github/workflows/sync.yaml",
    "chars": 421,
    "preview": "name: Sync Code to Gitee\n\non:\n  workflow_dispatch:\n  push:\n    branches: [master]\n\njobs:\n  sync-code-to-gitee:\n    runs-"
  },
  {
    "path": "README.md",
    "chars": 446,
    "preview": "# Snell 管理脚本\n\n> 纯自用。\n\n## 默认\n```bash\nwget -O snell.sh --no-check-certificate https://git.io/Snell.sh && chmod +x snell.sh"
  },
  {
    "path": "Snell.sh",
    "chars": 51418,
    "preview": "#!/usr/bin/env bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n\n#=============="
  }
]

About this extraction

This page contains the full source code of the xOS/Snell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (57.2 KB), approximately 18.3k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!