├── .github ├── FUNDING.yml └── workflows │ ├── sync.py │ └── sync.yaml ├── README.md └── Snell.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [xOS]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/sync.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import requests 4 | import hashlib 5 | from github import Github 6 | 7 | 8 | def get_github_latest_release(): 9 | g = Github() 10 | repo = g.get_repo("xOS/Snell") 11 | release = repo.get_latest_release() 12 | if release: 13 | print(f"Latest release tag is: {release.tag_name}") 14 | print(f"Latest release info is: {release.body}") 15 | files = [] 16 | for asset in release.get_assets(): 17 | url = asset.browser_download_url 18 | name = asset.name 19 | 20 | response = requests.get(url) 21 | if response.status_code == 200: 22 | with open(name, 'wb') as f: 23 | f.write(response.content) 24 | print(f"Downloaded {name}") 25 | else: 26 | print(f"Failed to download {name}") 27 | file_abs_path = get_abs_path(asset.name) 28 | files.append(file_abs_path) 29 | print('Checking file integrities') 30 | verify_checksum(get_abs_path("checksums.txt")) 31 | sync_to_gitee(release.tag_name, release.body, files) 32 | else: 33 | print("No releases found.") 34 | 35 | 36 | def delete_gitee_releases(latest_id, client, uri, token): 37 | get_data = { 38 | 'access_token': token 39 | } 40 | 41 | release_info = [] 42 | release_response = client.get(uri, json=get_data) 43 | if release_response.status_code == 200: 44 | release_info = release_response.json() 45 | else: 46 | print( 47 | f"Request failed with status code {release_response.status_code}") 48 | 49 | release_ids = [] 50 | for block in release_info: 51 | if 'id' in block: 52 | release_ids.append(block['id']) 53 | 54 | print(f'Current release ids: {release_ids}') 55 | release_ids.remove(latest_id) 56 | 57 | for id in release_ids: 58 | release_uri = f"{uri}/{id}" 59 | delete_data = { 60 | 'access_token': token 61 | } 62 | delete_response = client.delete(release_uri, json=delete_data) 63 | if delete_response.status_code == 204: 64 | print(f'Successfully deleted release #{id}.') 65 | else: 66 | raise ValueError( 67 | f"Request failed with status code {delete_response.status_code}") 68 | 69 | 70 | def sync_to_gitee(tag: str, body: str, files: slice): 71 | release_id = "" 72 | owner = "Ten" 73 | repo = "ServerAgent" 74 | release_api_uri = f"https://gitee.com/api/v5/repos/{owner}/{repo}/releases" 75 | api_client = requests.Session() 76 | api_client.headers.update({ 77 | 'Accept': 'application/json', 78 | 'Content-Type': 'application/json' 79 | }) 80 | 81 | access_token = os.environ['GITEE_TOKEN'] 82 | release_data = { 83 | 'access_token': access_token, 84 | 'tag_name': tag, 85 | 'name': tag, 86 | 'body': body, 87 | 'prerelease': False, 88 | 'target_commitish': 'master' 89 | } 90 | while True: 91 | try: 92 | release_api_response = api_client.post( 93 | release_api_uri, json=release_data, timeout=30) 94 | release_api_response.raise_for_status() 95 | break 96 | except requests.exceptions.Timeout as errt: 97 | print(f"Request timed out: {errt} Retrying in 60 seconds...") 98 | time.sleep(60) 99 | except requests.exceptions.RequestException as err: 100 | print(f"Request failed: {err}") 101 | break 102 | if release_api_response.status_code == 201: 103 | release_info = release_api_response.json() 104 | release_id = release_info.get('id') 105 | else: 106 | print( 107 | f"Request failed with status code {release_api_response.status_code}") 108 | 109 | print(f"Gitee release id: {release_id}") 110 | asset_api_uri = f"{release_api_uri}/{release_id}/attach_files" 111 | 112 | for file_path in files: 113 | success = False 114 | 115 | while not success: 116 | files = { 117 | 'file': open(file_path, 'rb') 118 | } 119 | 120 | asset_api_response = requests.post( 121 | asset_api_uri, params={'access_token': access_token}, files=files) 122 | 123 | if asset_api_response.status_code == 201: 124 | asset_info = asset_api_response.json() 125 | asset_name = asset_info.get('name') 126 | print(f"Successfully uploaded {asset_name}!") 127 | success = True 128 | else: 129 | print( 130 | f"Request failed with status code {asset_api_response.status_code}") 131 | 132 | # 仅保留最新 Release 以防超出 Gitee 仓库配额 133 | try: 134 | delete_gitee_releases(release_id, api_client, 135 | release_api_uri, access_token) 136 | except ValueError as e: 137 | print(e) 138 | 139 | api_client.close() 140 | print("Sync is completed!") 141 | 142 | 143 | def get_abs_path(path: str): 144 | wd = os.getcwd() 145 | return os.path.join(wd, path) 146 | 147 | 148 | def compute_sha256(file: str): 149 | sha256_hash = hashlib.sha256() 150 | buf_size = 65536 151 | with open(file, 'rb') as f: 152 | while True: 153 | data = f.read(buf_size) 154 | if not data: 155 | break 156 | sha256_hash.update(data) 157 | return sha256_hash.hexdigest() 158 | 159 | 160 | def verify_checksum(checksum_file: str): 161 | with open(checksum_file, 'r') as f: 162 | lines = f.readlines() 163 | 164 | for line in lines: 165 | checksum, file = line.strip().split() 166 | abs_path = get_abs_path(file) 167 | computed_hash = compute_sha256(abs_path) 168 | 169 | if checksum == computed_hash: 170 | print(f"{file}: OK") 171 | else: 172 | print(f"{file}: FAIL (expected {checksum}, got {computed_hash})") 173 | print("Will run the download process again") 174 | get_github_latest_release() 175 | break 176 | 177 | 178 | get_github_latest_release() 179 | -------------------------------------------------------------------------------- /.github/workflows/sync.yaml: -------------------------------------------------------------------------------- 1 | name: Sync Code to Gitee 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | sync-code-to-gitee: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: adambirds/sync-github-to-gitlab-action@v1.1.0 14 | with: 15 | destination_repository: git@gitee.com:Ten/Snell.git 16 | destination_branch_name: master 17 | destination_ssh_key: ${{ secrets.GITEE_SSH_KEY }} 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snell 管理脚本 2 | 3 | > 纯自用。 4 | 5 | ## 默认 6 | ```bash 7 | wget -O snell.sh --no-check-certificate https://git.io/Snell.sh && chmod +x snell.sh && ./snell.sh 8 | ``` 9 | ## 国内 10 | ```bash 11 | wget -O snell.sh --no-check-certificate https://gitee.com/ten/Snell/raw/master/Snell.sh && chmod +x snell.sh && ./snell.sh 12 | ``` 13 | 14 | ## 注意 15 | * 请手动放行防火墙相应端口。 16 | -------------------------------------------------------------------------------- /Snell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 3 | export PATH 4 | 5 | #================================================= 6 | # System Required: CentOS/Debian/Ubuntu 7 | # Description: Snell Server 管理脚本 8 | # Author: 翠花 9 | # WebSite: https://about.nange.cn 10 | #================================================= 11 | 12 | sh_ver="1.6.8" 13 | snell_version="4.1.1" 14 | script_dir=$(cd "$(dirname "$0")"; pwd) 15 | script_path=$(echo -e "${script_dir}"|awk -F "$0" '{print $1}') 16 | snell_dir="/etc/snell/" 17 | snell_bin="/usr/local/bin/snell-server" 18 | snell_conf="/etc/snell/config.conf" 19 | snell_version_file="/etc/snell/ver.txt" 20 | sysctl_conf="/etc/sysctl.d/local.conf" 21 | 22 | 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" 23 | Info="${Green_font_prefix}[信息]${Font_color_suffix}" 24 | Error="${Red_font_prefix}[错误]${Font_color_suffix}" 25 | Tip="${Yellow_font_prefix}[注意]${Font_color_suffix}" 26 | 27 | # 检查是否为 Root 用户 28 | checkRoot(){ 29 | [[ $EUID != 0 ]] && echo -e "${Error} 当前非ROOT账号(或没有ROOT权限),无法继续操作,请更换ROOT账号或使用 ${Green_background_prefix}sudo su${Font_color_suffix} 命令获取临时ROOT权限(执行后可能会提示输入当前账号的密码)。" && exit 1 30 | } 31 | 32 | # 检查系统类型 33 | checkSys(){ 34 | if [[ -f /etc/redhat-release ]]; then 35 | release="centos" 36 | elif cat /etc/issue | grep -q -E -i "debian"; then 37 | release="debian" 38 | elif cat /etc/issue | grep -q -E -i "ubuntu"; then 39 | release="ubuntu" 40 | elif cat /etc/issue | grep -q -E -i "centos|red hat|redhat"; then 41 | release="centos" 42 | elif cat /proc/version | grep -q -E -i "debian"; then 43 | release="debian" 44 | elif cat /proc/version | grep -q -E -i "ubuntu"; then 45 | release="ubuntu" 46 | elif cat /proc/version | grep -q -E -i "centos|red hat|redhat"; then 47 | release="centos" 48 | fi 49 | } 50 | 51 | # 检查依赖 52 | checkDependencies(){ 53 | local deps=("wget" "unzip" "ss") 54 | for cmd in "${deps[@]}"; do 55 | if ! command -v "$cmd" &> /dev/null; then 56 | echo -e "${Error} 缺少依赖: $cmd,正在尝试安装..." 57 | if [[ -f /etc/debian_version ]]; then 58 | apt-get update && apt-get install -y "$cmd" 59 | elif [[ -f /etc/redhat-release ]]; then 60 | yum install -y "$cmd" 61 | else 62 | echo -e "${Error} 不支持的系统,无法自动安装 $cmd" 63 | exit 1 64 | fi 65 | fi 66 | done 67 | echo -e "${Info} 依赖检查完成" 68 | } 69 | 70 | # 安装依赖 71 | installDependencies(){ 72 | if [[ ${release} == "centos" ]]; then 73 | yum update 74 | yum install gzip wget curl unzip jq -y 75 | else 76 | apt-get update 77 | apt-get install gzip wget curl unzip jq -y 78 | fi 79 | sysctl -w net.core.rmem_max=26214400 80 | sysctl -w net.core.rmem_default=26214400 81 | \cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 82 | echo -e "${Info} 依赖安装完成" 83 | } 84 | 85 | # 检查系统架构 86 | sysArch() { 87 | uname=$(uname -m) 88 | if [[ "$uname" == "i686" ]] || [[ "$uname" == "i386" ]]; then 89 | arch="i386" 90 | elif [[ "$uname" == *"armv7"* ]] || [[ "$uname" == "armv6l" ]]; then 91 | arch="armv7l" 92 | elif [[ "$uname" == *"armv8"* ]] || [[ "$uname" == "aarch64" ]]; then 93 | arch="aarch64" 94 | else 95 | arch="amd64" 96 | fi 97 | } 98 | 99 | # 开启 TCP Fast Open 100 | enableTCPFastOpen() { 101 | kernel=$(uname -r | awk -F . '{print $1}') 102 | if [ "$kernel" -ge 3 ]; then 103 | echo 3 >/proc/sys/net/ipv4/tcp_fastopen 104 | [[ ! -e $sysctl_conf ]] && echo "fs.file-max = 51200 105 | net.core.rmem_max = 67108864 106 | net.core.wmem_max = 67108864 107 | net.core.rmem_default = 65536 108 | net.core.wmem_default = 65536 109 | net.core.netdev_max_backlog = 4096 110 | net.core.somaxconn = 4096 111 | net.ipv4.tcp_syncookies = 1 112 | net.ipv4.tcp_tw_reuse = 1 113 | net.ipv4.tcp_tw_recycle = 0 114 | net.ipv4.tcp_fin_timeout = 30 115 | net.ipv4.tcp_keepalive_time = 1200 116 | net.ipv4.ip_local_port_range = 10000 65000 117 | net.ipv4.tcp_max_syn_backlog = 4096 118 | net.ipv4.tcp_max_tw_buckets = 5000 119 | net.ipv4.tcp_fastopen = 3 120 | net.ipv4.tcp_rmem = 4096 87380 67108864 121 | net.ipv4.tcp_wmem = 4096 65536 67108864 122 | net.ipv4.tcp_mtu_probing = 1 123 | net.ipv4.tcp_ecn=1 124 | net.core.default_qdisc=fq 125 | net.ipv4.tcp_congestion_control = bbr" >>/etc/sysctl.d/local.conf && sysctl --system >/dev/null 2>&1 126 | else 127 | echo -e "$Error 系统内核版本过低,无法支持 TCP Fast Open!" 128 | fi 129 | } 130 | 131 | # 检查 Snell 是否安装 132 | checkInstalledStatus(){ 133 | [[ ! -e ${snell_bin} ]] && echo -e "${Error} Snell Server 没有安装,请检查!" && exit 1 134 | } 135 | 136 | # 检查 Snell 运行状态 137 | checkStatus(){ 138 | if systemctl is-active snell-server.service &> /dev/null; then 139 | status="running" 140 | else 141 | status="stopped" 142 | fi 143 | } 144 | 145 | 146 | # 获取 Snell v4 下载链接 147 | getSnellV4DownloadUrl(){ 148 | sysArch 149 | snell_v4_url="https://dl.nssurge.com/snell/snell-server-v${snell_version}-linux-${arch}.zip" 150 | } 151 | 152 | # 获取最新版本号 153 | getLatestVersion(){ 154 | getSnellV4DownloadUrl 155 | filename=$(basename "${snell_v4_url}") 156 | if [[ $filename =~ v([0-9]+\.[0-9]+\.[0-9]+(rc[0-9]*|b[0-9]*)?) ]]; then 157 | new_ver=${BASH_REMATCH[1]} 158 | echo -e "${Info} 检测到 Snell 最新版本为 [ ${new_ver} ]" 159 | else 160 | echo -e "${Error} Snell Server 最新版本获取失败!" 161 | fi 162 | } 163 | 164 | # 下载并安装 Snell v2(备用源) 165 | downloadSnellV2() { 166 | echo -e "${Info} 开始下载 Snell Server v2..." 167 | wget --no-check-certificate -N "https://raw.githubusercontent.com/xOS/Others/master/snell/v2.0.6/snell-server-v2.0.6-linux-${arch}.zip" 168 | if [[ $? -ne 0 ]]; then 169 | echo -e "${Error} 下载 Snell Server v2 失败!" 170 | return 1 171 | fi 172 | unzip -o "snell-server-v2.0.6-linux-${arch}.zip" 173 | if [[ $? -ne 0 ]]; then 174 | echo -e "${Error} 解压 Snell Server v2 失败!" 175 | return 1 176 | fi 177 | rm -rf "snell-server-v2.0.6-linux-${arch}.zip" 178 | chmod +x snell-server 179 | mv -f snell-server "${snell_bin}" 180 | echo "v2.0.6" > "${snell_version_file}" 181 | echo -e "${Info} Snell Server v2 下载安装完毕!" 182 | return 0 183 | } 184 | 185 | 186 | # 下载并安装 Snell v3(备用源) 187 | downloadSnellV3() { 188 | echo -e "${Info} 试图请求 ${Yellow_font_prefix}v3 备用源版${Font_color_suffix} Snell Server ……" 189 | wget --no-check-certificate -N "https://raw.githubusercontent.com/xOS/Others/master/snell/v3.0.1/snell-server-v3.0.1-linux-${arch}.zip" 190 | if [[ ! -e "snell-server-v3.0.1-linux-${arch}.zip" ]]; then 191 | echo -e "${Error} Snell Server ${Yellow_font_prefix}v3 备用源版${Font_color_suffix} 下载失败!" 192 | return 1 && exit 1 193 | else 194 | unzip -o "snell-server-v3.0.1-linux-${arch}.zip" 195 | fi 196 | if [[ ! -e "snell-server" ]]; then 197 | echo -e "${Error} Snell Server ${Yellow_font_prefix}v3 备用源版${Font_color_suffix} 解压失败!" 198 | return 1 && exit 1 199 | else 200 | rm -rf "snell-server-v3.0.1-linux-${arch}.zip" 201 | chmod +x snell-server 202 | mv -f snell-server "${snell_bin}" 203 | echo "v3.0.1" > ${snell_version_file} 204 | echo -e "${Info} Snell Server 主程序下载安装完毕!" 205 | return 0 206 | fi 207 | } 208 | 209 | # 下载并安装 Snell v4(官方源) 210 | downloadSnellV4(){ 211 | echo -e "${Info} 试图请求 ${Yellow_font_prefix}v4 官网源版${Font_color_suffix} Snell Server ……" 212 | getLatestVersion 213 | wget --no-check-certificate -N "${snell_v4_url}" 214 | if [[ ! -e "snell-server-v${new_ver}-linux-${arch}.zip" ]]; then 215 | echo -e "${Error} Snell Server ${Yellow_font_prefix}v4 官网源版${Font_color_suffix} 下载失败!" 216 | return 1 && exit 1 217 | else 218 | unzip -o "snell-server-v${new_ver}-linux-${arch}.zip" 219 | fi 220 | if [[ ! -e "snell-server" ]]; then 221 | echo -e "${Error} Snell Server ${Yellow_font_prefix}v4 官网源版${Font_color_suffix} 解压失败!" 222 | return 1 && exit 1 223 | else 224 | rm -rf "snell-server-v${new_ver}-linux-${arch}.zip" 225 | chmod +x snell-server 226 | mv -f snell-server "${snell_bin}" 227 | echo "v${new_ver}" > ${snell_version_file} 228 | echo -e "${Info} Snell Server 主程序下载安装完毕!" 229 | return 0 230 | fi 231 | } 232 | 233 | # 安装 Snell 234 | installSnell() { 235 | if [[ ! -e "${snell_dir}" ]]; then 236 | mkdir "${snell_dir}" 237 | else 238 | [[ -e "${snell_bin}" ]] && rm -rf "${snell_bin}" 239 | fi 240 | echo -e "选择安装版本${Yellow_font_prefix}[2-4]${Font_color_suffix} 241 | ================================== 242 | ${Green_font_prefix} 2.${Font_color_suffix} v2 ${Green_font_prefix} 3.${Font_color_suffix} v3 ${Green_font_prefix} 4.${Font_color_suffix} v4 243 | ==================================" 244 | read -e -p "(默认:4.v4):" ver 245 | [[ -z "${ver}" ]] && ver="4" 246 | if [[ ${ver} == "2" ]]; then 247 | installSnellV2 248 | elif [[ ${ver} == "3" ]]; then 249 | installSnellV3 250 | elif [[ ${ver} == "4" ]]; then 251 | installSnellV4 252 | else 253 | installSnellV4 254 | fi 255 | } 256 | 257 | # 配置服务 258 | setupService(){ 259 | echo ' 260 | [Unit] 261 | Description=Snell Service 262 | After=network-online.target 263 | Wants=network-online.target systemd-networkd-wait-online.service 264 | [Service] 265 | LimitNOFILE=32767 266 | Type=simple 267 | User=root 268 | Restart=on-failure 269 | RestartSec=5s 270 | ExecStartPre=/bin/sh -c 'ulimit -n 51200' 271 | ExecStart=/usr/local/bin/snell-server -c /etc/snell/config.conf 272 | [Install] 273 | WantedBy=multi-user.target' > /etc/systemd/system/snell-server.service 274 | systemctl enable --now snell-server 275 | echo -e "${Info} Snell Server 服务配置完成!" 276 | } 277 | 278 | # 写入配置文件 279 | writeConfig(){ 280 | if [[ -f "${snell_conf}" ]]; then 281 | cp "${snell_conf}" "${snell_conf}.bak.$(date +%Y%m%d_%H%M%S)" 282 | echo -e "${Info} 已备份旧配置文件到 ${snell_conf}.bak" 283 | fi 284 | cat > "${snell_conf}" << EOF 285 | [snell-server] 286 | listen = ::0:${port} 287 | ipv6 = ${ipv6} 288 | psk = ${psk} 289 | obfs = ${obfs} 290 | $(if [[ ${obfs} != "off" ]]; then echo "obfs-host = ${host}"; fi) 291 | tfo = ${tfo} 292 | dns = ${dns} 293 | version = ${ver} 294 | EOF 295 | } 296 | 297 | 298 | 299 | 300 | # 读取配置文件 301 | readConfig(){ 302 | [[ ! -e ${snell_conf} ]] && echo -e "${Error} Snell Server 配置文件不存在!" && exit 1 303 | ipv6=$(cat ${snell_conf}|grep 'ipv6 = '|awk -F 'ipv6 = ' '{print $NF}') 304 | port=$(grep -E '^listen\s*=' ${snell_conf} | awk -F ':' '{print $NF}' | xargs) 305 | psk=$(cat ${snell_conf}|grep 'psk = '|awk -F 'psk = ' '{print $NF}') 306 | obfs=$(cat ${snell_conf}|grep 'obfs = '|awk -F 'obfs = ' '{print $NF}') 307 | host=$(cat ${snell_conf}|grep 'obfs-host = '|awk -F 'obfs-host = ' '{print $NF}') 308 | tfo=$(cat ${snell_conf}|grep 'tfo = '|awk -F 'tfo = ' '{print $NF}') 309 | dns=$(cat ${snell_conf}|grep 'dns = '|awk -F 'dns = ' '{print $NF}') 310 | ver=$(cat ${snell_conf}|grep 'version = '|awk -F 'version = ' '{print $NF}') 311 | } 312 | 313 | # 设置端口 314 | setPort(){ 315 | while true; do 316 | echo -e "${Tip} 本步骤不涉及系统防火墙端口操作,请手动放行相应端口!" 317 | echo -e "请输入 Snell Server 端口${Yellow_font_prefix}[1-65535]${Font_color_suffix}" 318 | read -e -p "(默认: 2345):" port 319 | [[ -z "${port}" ]] && port="2345" 320 | if [[ $port =~ ^[0-9]+$ ]] && [[ $port -ge 1 && $port -le 65535 ]]; then 321 | if ss -tuln | grep -q ":$port "; then 322 | echo -e "${Error} 端口 $port 已被占用,请选择其他端口。" 323 | else 324 | echo && echo "==============================" 325 | echo -e "端口 : ${Red_background_prefix} ${port} ${Font_color_suffix}" 326 | echo "==============================" && echo 327 | break 328 | fi 329 | else 330 | echo "输入错误, 请输入正确的端口号。" 331 | sleep 2s 332 | setPort 333 | fi 334 | done 335 | } 336 | 337 | 338 | # 设置 IPv6 339 | setIpv6(){ 340 | echo -e "是否开启 IPv6 解析? 341 | ================================== 342 | ${Green_font_prefix} 1.${Font_color_suffix} 开启 ${Green_font_prefix} 2.${Font_color_suffix} 关闭 343 | ==================================" 344 | read -e -p "(默认:2.关闭):" ipv6 345 | [[ -z "${ipv6}" ]] && ipv6="false" 346 | if [[ ${ipv6} == "1" ]]; then 347 | ipv6=true 348 | else 349 | ipv6=false 350 | fi 351 | echo && echo "==================================" 352 | echo -e "IPv6 解析 开启状态:${Red_background_prefix} ${ipv6} ${Font_color_suffix}" 353 | echo "==================================" && echo 354 | } 355 | 356 | # 设置密钥 357 | setPSK(){ 358 | echo "请输入 Snell Server 密钥 [0-9][a-z][A-Z] " 359 | read -e -p "(默认: 随机生成):" psk 360 | [[ -z "${psk}" ]] && psk=$(tr -dc A-Za-z0-9