├── install.ps1 ├── package.json ├── index.js ├── README.md ├── 1.py ├── install.sh ├── start.sh └── q.sh /install.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byJoey/ladefree/HEAD/install.ps1 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-sb", 3 | "version": "1.0.0", 4 | "description": "node", 5 | "main": "index.js", 6 | "author": "eooce", 7 | "license": "MIT", 8 | "private": false, 9 | "scripts": { 10 | "start": "node index.js" 11 | }, 12 | "engines": { 13 | "node": ">=14" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const exec = require("child_process").exec; 4 | const subtxt = './.npm/sub.txt' 5 | const PORT = process.env.PORT || 3000; 6 | 7 | // Run start.sh 8 | fs.chmod("start.sh", 0o777, (err) => { 9 | if (err) { 10 | console.error(`start.sh empowerment failed: ${err}`); 11 | return; 12 | } 13 | console.log(`start.sh empowerment successful`); 14 | const child = exec('bash start.sh'); 15 | child.stdout.on('data', (data) => { 16 | console.log(data); 17 | }); 18 | child.stderr.on('data', (data) => { 19 | console.error(data); 20 | }); 21 | child.on('close', (code) => { 22 | console.log(`child process exited with code ${code}`); 23 | console.clear() 24 | console.log(`App is running`); 25 | }); 26 | }); 27 | 28 | // create HTTP server 29 | const server = http.createServer((req, res) => { 30 | if (req.url === '/') { 31 | res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); 32 | res.end('Hello world!'); 33 | } 34 | // get-sub 35 | if (req.url === '/sub') { 36 | fs.readFile(subtxt, 'utf8', (err, data) => { 37 | if (err) { 38 | console.error(err); 39 | res.writeHead(500, { 'Content-Type': 'application/json' }); 40 | res.end(JSON.stringify({ error: 'Error reading sub.txt' })); 41 | } else { 42 | res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); 43 | res.end(data); 44 | } 45 | }); 46 | } 47 | }); 48 | 49 | server.listen(PORT, () => { 50 | console.log(`Server is running on port ${PORT}`); 51 | }); 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Lade CLI 多功能管理脚本 3 | 4 | 这是一个跨平台的 Bash 和 PowerShell 脚本,旨在简化 Lade 应用的部署和日常管理。它自动化了 Lade CLI 的安装、应用部署、查看、删除和日志管理等常用操作,让你的开发和运维工作更加高效。 5 | 6 | ----- 7 | 8 | ## 主要功能 9 | 10 | * **Lade CLI 自动安装:** 自动检测并安装最新版本的 Lade CLI 工具(支持 Linux、macOS 和 Windows)。 11 | * **应用部署:** 快速部署 Ladefree 应用到 Lade 平台,支持创建新应用或更新现有应用。此过程不依赖本地 Git,直接通过 ZIP 包下载部署。 12 | * **应用管理:** 13 | * 查看所有已部署的 Lade 应用。 14 | * **删除**指定的 Lade 应用 (`lade apps remove`)。 15 | * 查看应用实时日志。 16 | * **登录状态刷新:** 检查并提示你刷新 Lade 登录会话。 17 | * **跨平台支持:** 提供 Bash 和 PowerShell 两个版本,兼容主流操作系统。 18 | 19 | ----- 20 | 21 | ## 如何使用 22 | 23 | 你可以根据你的操作系统选择使用 Bash 或 PowerShell 脚本。 24 | 25 | ### Bash 版本 (Linux / macOS) 26 | 27 | 1. **一行命令运行脚本:** 28 | 直接在终端中运行以下命令即可自动下载并执行脚本。 29 | 30 | ```bash 31 | bash <(curl -l -s https://raw.githubusercontent.com/byJoey/ladefree/refs/heads/main/install.sh) 32 | ``` 33 | 34 | 35 | 2. **脚本运行步骤:** 36 | 37 | * 脚本会首先检查并安装 **Lade CLI**。你可能需要输入 `sudo` 密码来完成安装。 38 | * 安装完成后,将显示主菜单,你可以根据提示选择相应操作。 39 | 40 | ### PowerShell 版本 (Windows) 41 | 42 | 1. **以管理员身份运行 PowerShell:** 43 | 为了确保 Lade CLI 能够正确安装到系统路径,并执行其他需要权限的操作,**强烈建议以管理员身份运行 PowerShell**。 44 | 45 | 2. **一行命令运行脚本:** 46 | **重要安全提示:** 以下命令使用了 `-ExecutionPolicy Bypass` 参数,它会绕过 PowerShell 的执行策略,允许运行任何脚本。**请确保你完全信任此脚本的来源,否则可能存在安全风险。** 47 | 48 | 打开 PowerShell 并运行以下命令: 49 | 50 | ```powershell 51 | Invoke-WebRequest -Uri "https://raw.githubusercontent.com/byJoey/ladefree/main/install.ps1" -OutFile "$env:TEMP\install.ps1"; PowerShell -ExecutionPolicy Bypass -File "$env:TEMP\install.ps1"; Remove-Item "$env:TEMP\install.ps1" -ErrorAction SilentlyContinue 52 | ``` 53 | 54 | * 此命令将脚本下载到临时文件夹。 55 | * 然后使用 `-ExecutionPolicy Bypass` 参数执行该脚本。 56 | * 最后,它会清理临时下载的脚本文件。 57 | 58 | 3. **脚本运行步骤:** 59 | 60 | * 脚本会首先检查并安装 **Lade CLI**。在安装过程中,你可能会看到权限提升请求,请允许。 61 | * 安装完成后,将显示主菜单,你可以根据提示选择相应操作。 62 | 63 | ----- 64 | 65 | ## 贡献 66 | 67 | 如果你有任何改进建议或发现 Bug,欢迎通过 GitHub Issues 或 Pull Requests 提交。 68 | 69 | ----- 70 | ## 感谢老王的notejs https://github.com/eooce 71 | 72 | ## 作者 73 | 74 | * **Joey** 75 | * 博客: [joeyblog.net](https://joeyblog.net) 76 | * Telegram 群: [https://t.me/+ft-zI76oovgwNmRh](https://t.me/+ft-zI76oovgwNmRh) 77 | 78 | ----- 79 | 80 | ## 许可 81 | 82 | 此项目根据 MIT 许可证发布 - 详情请参阅 [LICENSE](https://www.google.com/search?q=LICENSE) 文件。 83 | 84 | ----- 85 | -------------------------------------------------------------------------------- /1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | 简化版WARP集成脚本 4 | 适用于JupyterLab环境 5 | """ 6 | 7 | import os 8 | import sys 9 | import json 10 | import time 11 | import subprocess 12 | import platform 13 | import requests 14 | 15 | def get_architecture(): 16 | """获取系统架构""" 17 | arch = platform.machine().lower() 18 | if arch in ['x86_64', 'amd64']: 19 | return 'amd64' 20 | elif arch in ['aarch64', 'arm64']: 21 | return 'arm64' 22 | else: 23 | return 'amd64' 24 | 25 | def download_warp_client(): 26 | """下载WARP客户端""" 27 | arch = get_architecture() 28 | url = f"https://github.com/bepass-org/warp-plus/releases/latest/download/warp-plus_linux-{arch}" 29 | filename = "warp-plus" 30 | 31 | try: 32 | print(f"正在下载 WARP 客户端 ({arch})...") 33 | response = requests.get(url, stream=True, timeout=60) 34 | response.raise_for_status() 35 | 36 | with open(filename, 'wb') as f: 37 | for chunk in response.iter_content(chunk_size=8192): 38 | f.write(chunk) 39 | 40 | os.chmod(filename, 0o755) 41 | print("✅ WARP客户端下载成功") 42 | return True 43 | 44 | except Exception as e: 45 | print(f"❌ 下载失败: {e}") 46 | return False 47 | 48 | def start_warp_service(): 49 | """启动WARP服务""" 50 | if not os.path.exists("warp-plus"): 51 | print("❌ WARP客户端不存在") 52 | return False, None 53 | 54 | try: 55 | print("🚀 正在启动 WARP 服务...") 56 | 57 | # 启动WARP 58 | process = subprocess.Popen( 59 | ["./warp-plus", "--bind", "127.0.0.1:40000"], 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE, 62 | preexec_fn=os.setsid if hasattr(os, 'setsid') else None 63 | ) 64 | 65 | # 等待启动 66 | time.sleep(5) 67 | 68 | if process.poll() is None: 69 | print(f"✅ WARP服务启动成功,PID: {process.pid}") 70 | return True, process.pid 71 | else: 72 | stdout, stderr = process.communicate() 73 | print(f"❌ WARP启动失败: {stderr.decode()}") 74 | return False, None 75 | 76 | except Exception as e: 77 | print(f"❌ 启动WARP失败: {e}") 78 | return False, None 79 | 80 | def test_warp_connection(): 81 | """测试WARP连接""" 82 | try: 83 | import socket 84 | 85 | # 简单的端口连通性测试 86 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 87 | sock.settimeout(3) 88 | result = sock.connect_ex(('127.0.0.1', 40000)) 89 | sock.close() 90 | 91 | if result == 0: 92 | print("✅ WARP代理端口连通性测试成功") 93 | return True 94 | else: 95 | print("❌ WARP代理端口不可访问") 96 | return False 97 | 98 | except Exception as e: 99 | print(f"❌ 连接测试失败: {e}") 100 | return False 101 | 102 | def update_xray_config(): 103 | """更新Xray配置以支持WARP""" 104 | config_files = ["config.json", ".cache/config.json", "python-xray-argo/config.json"] 105 | 106 | for config_file in config_files: 107 | if os.path.exists(config_file): 108 | try: 109 | print(f"📝 正在更新配置文件: {config_file}") 110 | 111 | with open(config_file, 'r', encoding='utf-8') as f: 112 | config = json.load(f) 113 | 114 | # 添加WARP outbound 115 | warp_outbound = { 116 | "protocol": "socks", 117 | "settings": { 118 | "servers": [{"address": "127.0.0.1", "port": 40000}] 119 | }, 120 | "tag": "warp" 121 | } 122 | 123 | if "outbounds" not in config: 124 | config["outbounds"] = [] 125 | 126 | # 检查是否已存在WARP配置 127 | warp_exists = any(out.get("tag") == "warp" for out in config["outbounds"]) 128 | if not warp_exists: 129 | config["outbounds"].insert(1, warp_outbound) 130 | 131 | # 添加路由规则 132 | youtube_rule = { 133 | "type": "field", 134 | "domain": [ 135 | "youtube.com", 136 | "youtu.be", 137 | "googlevideo.com", 138 | "ytimg.com" 139 | ], 140 | "outboundTag": "warp" 141 | } 142 | 143 | if "routing" not in config: 144 | config["routing"] = {"rules": []} 145 | 146 | # 检查是否已存在YouTube路由规则 147 | youtube_rule_exists = any( 148 | rule.get("outboundTag") == "warp" and 149 | "youtube.com" in rule.get("domain", []) 150 | for rule in config["routing"]["rules"] 151 | ) 152 | 153 | if not youtube_rule_exists: 154 | config["routing"]["rules"].insert(0, youtube_rule) 155 | 156 | # 保存配置 157 | with open(config_file, 'w', encoding='utf-8') as f: 158 | json.dump(config, f, ensure_ascii=False, indent=2) 159 | 160 | print(f"✅ 配置文件更新成功: {config_file}") 161 | return True 162 | 163 | except Exception as e: 164 | print(f"❌ 更新配置失败 {config_file}: {e}") 165 | continue 166 | 167 | print("⚠️ 未找到Xray配置文件") 168 | return False 169 | 170 | def main(): 171 | """主函数""" 172 | print("🌐 WARP集成工具 - 简化版") 173 | print("适用于JupyterLab和无root权限环境\n") 174 | 175 | # 询问用户 176 | choice = input("是否启用WARP SOCKS5代理? (y/n): ").lower().strip() 177 | 178 | if choice not in ['y', 'yes', '1']: 179 | print("跳过WARP配置") 180 | return 181 | 182 | # 下载WARP客户端 183 | if not download_warp_client(): 184 | print("❌ WARP客户端下载失败") 185 | return 186 | 187 | # 启动WARP服务 188 | success, pid = start_warp_service() 189 | 190 | if not success: 191 | print("❌ WARP服务启动失败") 192 | return 193 | 194 | # 测试连接 195 | if test_warp_connection(): 196 | print("✅ WARP代理测试成功") 197 | else: 198 | print("⚠️ WARP代理测试失败,但服务已启动") 199 | 200 | # 更新Xray配置 201 | if update_xray_config(): 202 | print("✅ Xray配置更新成功") 203 | else: 204 | print("⚠️ Xray配置更新失败") 205 | 206 | print(f"\n🎉 WARP配置完成!") 207 | print(f"📊 WARP服务PID: {pid}") 208 | print(f"🌐 代理地址: 127.0.0.1:40000") 209 | print(f"🎯 YouTube等网站将通过WARP访问") 210 | 211 | print(f"\n🔧 管理命令:") 212 | print(f" 查看WARP进程: ps -p {pid}") 213 | print(f" 停止WARP: kill {pid}") 214 | print(f" 重启WARP: kill {pid} && ./warp-plus --bind 127.0.0.1:40000 &") 215 | 216 | if __name__ == "__main__": 217 | try: 218 | main() 219 | except KeyboardInterrupt: 220 | print("\n\n👋 用户取消操作") 221 | except Exception as e: 222 | print(f"\n❌ 程序执行出错: {e}") 223 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | GREEN='\033[0;32m' 6 | BLUE='\033[0;34m' 7 | YELLOW='\033[0;33m' 8 | RED='\033[0;31m' 9 | PURPLE='\033[0;35m' 10 | CYAN='\033[0;36m' 11 | NC='\033[0m' 12 | 13 | LADEFREE_REPO_URL_BASE="https://github.com/byJoey/ladefree" 14 | LADEFREE_REPO_BRANCH="main" 15 | LADE_CLI_NAME="lade" 16 | LADE_INSTALL_PATH="/usr/local/bin/${LADE_CLI_NAME}" 17 | 18 | display_welcome() { 19 | clear 20 | echo -e "${CYAN}#############################################################${NC}" 21 | echo -e "${CYAN}#${NC} ${CYAN}#${NC}" 22 | echo -e "${CYAN}#${NC} ${BLUE}欢迎使用 Lade CLI 多功能管理脚本 v1.0.0${NC} ${CYAN}#${NC}" 23 | echo -e "${CYAN}#${NC} ${CYAN}#${NC}" 24 | echo -e "${CYAN}#############################################################${NC}" 25 | echo -e "${GREEN}" 26 | echo " >> 作者: Joey" 27 | echo " >> 博客: joeyblog.net" 28 | echo " >> Telegram 群: https://t.me/+ft-zI76oovgwNmRh" 29 | echo -e "${NC}" 30 | echo -e "${YELLOW}这是一个自动化 Lade 应用部署和管理工具,旨在简化操作。${NC}" 31 | echo "" 32 | read -p "按 Enter 键开始..." 33 | } 34 | 35 | display_section_header() { 36 | echo "" 37 | echo -e "${PURPLE}--- ${1} ---${NC}" 38 | echo -e "${PURPLE}-----------------------------------${NC}" 39 | } 40 | 41 | command_exists() { 42 | command -v "$1" &> /dev/null 43 | } 44 | 45 | # 新增函数:自动安装缺失的软件包 46 | install_package() { 47 | local package_name="$1" 48 | echo -e "${YELLOW}正在尝试安装缺失的命令: '${package_name}'...${NC}" 49 | 50 | local os_type=$(uname -s | tr '[:upper:]' '[:lower:]') 51 | 52 | case "${os_type}" in 53 | linux) 54 | # Debian/Ubuntu 55 | if command_exists apt-get; then 56 | sudo apt-get update && sudo apt-get install -y "${package_name}" 57 | # RedHat/CentOS 58 | elif command_exists yum; then 59 | sudo yum install -y "${package_name}" 60 | # Fedora 61 | elif command_exists dnf; then 62 | sudo dnf install -y "${package_name}" 63 | else 64 | echo -e "${RED}错误:未找到支持的 Linux 包管理器(apt-get, yum, dnf)。请手动安装 '${package_name}'。${NC}" 65 | return 1 66 | fi 67 | ;; 68 | darwin) # macOS 69 | if command_exists brew; then 70 | brew install "${package_name}" 71 | else 72 | echo -e "${RED}错误:在 macOS 上未找到 Homebrew。请先安装 Homebrew (https://brew.sh/),然后手动安装 '${package_name}'。${NC}" 73 | return 1 74 | fi 75 | ;; 76 | *) 77 | echo -e "${RED}错误:不支持您的操作系统 '${os_type}' 的自动安装。请手动安装 '${package_name}'。${NC}" 78 | return 1 79 | ;; 80 | esac 81 | 82 | if ! command_exists "${package_name}"; then 83 | echo -e "${RED}错误:自动安装 '${package_name}' 失败。请手动安装后重试。${NC}" 84 | return 1 85 | fi 86 | echo -e "${GREEN}'${package_name}' 已成功安装。${NC}" 87 | return 0 88 | } 89 | 90 | check_lade_cli() { 91 | command_exists "$LADE_CLI_NAME" 92 | } 93 | 94 | ensure_lade_login() { 95 | echo "" 96 | echo -e "${PURPLE}--- 检查 Lade 登录状态 ---${NC}" 97 | if ! lade apps list; then 98 | echo -e "${RED}错误:Lade 未登录或登录失败。请手动运行 'lade login' 进行登录。${NC}" 99 | 100 | if [ $? -ne 0 ]; then 101 | echo -e "${RED}Lade 登录失败。请检查用户名/密码或网络连接。${NC}" 102 | exit 1 103 | fi 104 | echo -e "${GREEN}Lade 登录成功!${NC}" 105 | else 106 | echo -e "${GREEN}Lade 已登录。${NC}" 107 | fi 108 | } 109 | 110 | deploy_app() { 111 | display_section_header "部署 Lade 应用" 112 | 113 | ensure_lade_login 114 | 115 | read -p "请输入您要部署的 Lade 应用名称 (例如: my-ladefree-app): " LADE_APP_NAME 116 | if [ -z "$LADE_APP_NAME" ]; then 117 | echo -e "${YELLOW}应用名称不能为空。取消部署。${NC}" 118 | return 119 | fi 120 | 121 | echo "正在检查应用 '${LADE_APP_NAME}' 是否存在..." 122 | local app_exists="false" 123 | if lade apps list | grep -qw "${LADE_APP_NAME}"; then 124 | app_exists="true" 125 | fi 126 | 127 | if [ "${app_exists}" == "true" ]; then 128 | echo -e "${GREEN}应用 '${LADE_APP_NAME}' 已存在,将直接部署更新。${NC}" 129 | else 130 | echo -e "${YELLOW}应用 '${LADE_APP_NAME}' 不存在,将尝试创建新应用。${NC}" 131 | echo -e "${CYAN}注意:创建应用将交互式询问 'Plan' 和 'Region',请手动选择。${NC}" 132 | lade apps create "${LADE_APP_NAME}" 133 | if [ $? -ne 0 ]; then 134 | echo -e "${RED}错误:Lade 应用创建失败。请检查输入或应用名称是否可用。${NC}" 135 | return 136 | fi 137 | echo -e "${GREEN}Lade 应用创建命令已发送。${NC}" 138 | fi 139 | 140 | echo "" 141 | echo -e "${BLUE}--- 正在下载 ZIP 并部署 Ladefree 应用 (不依赖 Git) ---${NC}" 142 | # 使用当前目录下的一个子目录进行下载和解压 143 | local ladefree_temp_download_dir="./ladefree_temp_download" 144 | mkdir -p "${ladefree_temp_download_dir}" 145 | 146 | local ladefree_download_url="${LADEFREE_REPO_URL_BASE}/archive/refs/heads/${LADEFREE_REPO_BRANCH}.zip" 147 | local temp_ladefree_archive="${ladefree_temp_download_dir}/ladefree.zip" 148 | 149 | echo "正在下载 ${LADEFREE_REPO_URL_BASE} (${LADEFREE_REPO_BRANCH} 分支) 为 ZIP 包..." 150 | echo "下载 URL: ${ladefree_download_url}" 151 | 152 | if ! curl -L --fail -o "${temp_ladefree_archive}" "${ladefree_download_url}"; then 153 | echo -e "${RED}错误:下载 Ladefree 仓库 ZIP 包失败。请检查 URL 或网络连接。${NC}" 154 | rm -rf "${ladefree_temp_download_dir}" || true 155 | return 156 | fi 157 | 158 | echo "下载完成,正在解压..." 159 | if ! unzip -o "${temp_ladefree_archive}" -d "${ladefree_temp_download_dir}"; then # -o 选项用于覆盖现有文件 160 | echo -e "${RED}错误:解压 Ladefree ZIP 包失败。${NC}" 161 | rm -rf "${ladefree_temp_download_dir}" || true 162 | return 163 | fi 164 | 165 | # 查找解压后的应用程序目录,通常是 ladefree-main/ 或 ladefree-分支名/ 166 | local extracted_app_path=$(find "${ladefree_temp_download_dir}" -maxdepth 1 -type d -name "ladefree-*" -print -quit) 167 | 168 | if [ -z "${extracted_app_path}" ]; then 169 | echo -e "${RED}错误:未在临时下载目录中找到解压后的 Ladefree 应用程序目录。${NC}" 170 | rm -rf "${ladefree_temp_download_dir}" || true 171 | return 172 | fi 173 | 174 | echo -e "${BLUE}正在从本地解压路径 ${extracted_app_path} 部署到 Lade:${LADE_APP_NAME} ...${NC}" 175 | (cd "${extracted_app_path}" && lade deploy --app "${LADE_APP_NAME}") 176 | 177 | local deploy_status=$? 178 | 179 | echo "清理临时下载目录 ${ladefree_temp_download_dir}..." 180 | rm -rf "${ladefree_temp_download_dir}" || true 181 | 182 | if [ ${deploy_status} -ne 0 ]; then 183 | echo -e "${RED}错误:Lade 应用部署失败。请检查 Ladefree 代码本身的问题或 Lade 平台日志。${NC}" 184 | return 185 | fi 186 | echo -e "${GREEN}Lade 应用部署成功!${NC}" 187 | 188 | echo "" 189 | echo -e "${CYAN}--- 部署完成 ---${NC}" 190 | } 191 | 192 | view_apps() { 193 | display_section_header "查看所有 Lade 应用" 194 | 195 | ensure_lade_login 196 | 197 | lade apps list 198 | if [ $? -ne 0 ]; then 199 | echo -e "${RED}错误:无法获取应用列表。请检查网络或 Lade CLI 状态。${NC}" 200 | fi 201 | } 202 | 203 | delete_app() { 204 | display_section_header "删除 Lade 应用" 205 | 206 | ensure_lade_login 207 | 208 | read -p "请输入您要删除的 Lade 应用名称: " APP_TO_DELETE 209 | if [ -z "${APP_TO_DELETE}" ]; then 210 | echo -e "${YELLOW}应用名称不能为空。取消删除。${NC}" 211 | return 212 | fi 213 | 214 | echo -e "${RED}警告:您即将删除应用 '${APP_TO_DELETE}'。此操作不可撤销!${NC}" 215 | read -p "确定要删除吗? (y/N): " CONFIRM_DELETE 216 | CONFIRM_DELETE=$(echo "${CONFIRM_DELETE}" | tr '[:upper:]' '[:lower:]') 217 | 218 | if [ "${CONFIRM_DELETE}" == "y" ]; then 219 | lade apps remove "${APP_TO_DELETE}" 220 | if [ $? -ne 0 ]; then 221 | echo -e "${RED}错误:删除应用 '${APP_TO_DELETE}' 失败。请检查应用名称是否正确或您是否有权限。${NC}" 222 | else 223 | echo -e "${GREEN}应用 '${APP_TO_DELETE}' 已成功删除。${NC}" 224 | fi 225 | else 226 | echo -e "${YELLOW}取消删除操作。${NC}" 227 | fi 228 | } 229 | 230 | view_app_logs() { 231 | display_section_header "查看 Lade 应用日志" 232 | 233 | ensure_lade_login 234 | 235 | read -p "请输入您要查看日志的 Lade 应用名称: " APP_FOR_LOGS 236 | if [ -z "$APP_FOR_LOGS" ]; then 237 | echo -e "${YELLOW}应用名称不能为空。取消查看日志。${NC}" 238 | return 239 | fi 240 | 241 | echo -e "${CYAN}正在查看应用 '${APP_FOR_LOGS}' 的实时日志 (按 Ctrl+C 停止)...${NC}" 242 | lade logs -a "$APP_FOR_LOGS" -f 243 | if [ $? -ne 0 ]; then 244 | echo -e "${RED}错误:无法获取应用 '${APP_FOR_LOGS}' 的日志。请检查应用名称是否正确或应用是否正在运行。${NC}" 245 | fi 246 | } 247 | 248 | install_lade_cli() { 249 | display_section_header "检查或安装 Lade CLI" 250 | 251 | if ! command_exists curl; then install_package curl || exit 1; fi 252 | local os_type=$(uname -s | tr '[:upper:]' '[:lower:]') 253 | if [ "${os_type}" == "linux" ]; then 254 | if ! command_exists tar; then install_package tar || exit 1; fi 255 | if ! command_exists unzip; then install_package unzip || exit 1; fi 256 | elif [ "${os_type}" == "darwin" ]; then 257 | if ! command_exists tar; then install_package tar || exit 1; fi # macOS通常自带tar,但以防万一 258 | if ! command_exists unzip; then install_package unzip || exit 1; fi # macOS通常自带unzip,但以防万一 259 | elif [ "${os_type}" == "windows" ]; then 260 | 261 | echo -e "${YELLOW}注意:在 Windows 环境下,请确保 tar 和 unzip 命令可用。${NC}" 262 | if ! command_exists unzip; then 263 | echo -e "${RED}错误:Windows 环境未检测到 'unzip' 命令。请手动安装。${NC}" 264 | exit 1 265 | fi 266 | if ! command_exists tar; then 267 | echo -e "${RED}错误:Windows 环境未检测到 'tar' 命令。请手动安装。${NC}" 268 | exit 1 269 | fi 270 | fi 271 | if ! command_exists awk; then install_package awk || exit 1; fi 272 | 273 | # --- Lade CLI 检查与安装 --- 274 | if check_lade_cli; then 275 | echo -e "${GREEN}Lade CLI 已安装:$(which "$LADE_CLI_NAME")${NC}" 276 | return 0 277 | fi 278 | 279 | echo -e "${YELLOW}Lade CLI 未安装。正在尝试自动安装 Lade CLI...${NC}" 280 | 281 | local lade_release_url="https://github.com/lade-io/lade/releases" 282 | # 使用当前目录下的子目录进行下载和解压,而不是 /tmp 283 | local lade_temp_dir="./lade_cli_install_temp" 284 | mkdir -p "${lade_temp_dir}" 285 | 286 | local arch_type=$(uname -m) 287 | 288 | local arch_suffix="" 289 | local file_extension="" 290 | case "${os_type}" in 291 | darwin) 292 | if [ "${arch_type}" == "x86_64" ]; then 293 | arch_suffix="-amd64" 294 | echo -e "${BLUE}检测到 macOS Intel (x86_64) 架构。${NC}" 295 | elif [ "${arch_type}" == "arm64" ]; then 296 | arch_suffix="-arm64" 297 | echo -e "${BLUE}检测到 macOS ARM (arm64) 架构。${NC}" 298 | else 299 | echo -e "${RED}错误:不支持的 macOS 架构:${arch_type}${NC}" 300 | rm -rf "${lade_temp_dir}" || true 301 | exit 1 302 | fi 303 | file_extension=".tar.gz" ;; 304 | linux) 305 | if [ "${arch_type}" == "x86_64" ]; then 306 | arch_suffix="-amd64" 307 | echo -e "${BLUE}检测到 Linux AMD64 (x86_64) 架构。${NC}" 308 | elif [ "${arch_type}" == "aarch64" ]; then 309 | arch_suffix="-arm64" 310 | echo -e "${BLUE}检测到 Linux ARM (aarch64) 架构。${NC}" 311 | else 312 | echo -e "${RED}错误:不支持的 Linux 架构:${arch_type}${NC}" 313 | rm -rf "${lade_temp_dir}" || true 314 | exit 1 315 | fi 316 | file_extension=".tar.gz" ;; 317 | windows) 318 | if [ "${arch_type}" == "x86_64" ]; then 319 | arch_suffix="-amd64" 320 | echo -e "${BLUE}检测到 Windows AMD64 (x86_64) 架构。${NC}" 321 | elif [ "${arch_type}" == "aarch64" ]; then 322 | arch_suffix="-arm64" 323 | echo -e "${BLUE}检测到 Windows ARM (aarch64) 架构。${NC}" 324 | else 325 | echo -e "${RED}错误:不支持的 Windows 架构:${arch_type}${NC}" 326 | rm -rf "${lade_temp_dir}" || true 327 | exit 1 328 | fi 329 | file_extension=".zip" ;; 330 | *) echo -e "${RED}错误:不支持的操作系统:${os_type}${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1 ;; 331 | esac 332 | 333 | echo "正在获取最新版本的 Lade CLI..." 334 | local latest_release_tag=$(curl -s "https://api.github.com/repos/lade-io/lade/releases/latest" | awk -F'"' '/"tag_name":/{print $4}') 335 | if [ -z "${latest_release_tag}" ]; then echo -e "${RED}错误:无法获取最新版本的 Lade CLI。请检查网络或 GitHub API 限制。${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1; fi 336 | local lade_version="${latest_release_tag}" 337 | echo -e "${GREEN}检测到最新版本:${lade_version}${NC}" 338 | 339 | local filename_to_download="lade-${os_type}${arch_suffix}${file_extension}" 340 | local download_url="${lade_release_url}/download/${lade_version}/${filename_to_download}" 341 | local temp_archive="${lade_temp_dir}/${filename_to_download}" # 下载到子目录 342 | 343 | echo "下载 URL: ${download_url}" 344 | echo "正在下载 ${filename_to_download} 到 ${temp_archive}..." 345 | if ! curl -L --fail -o "${temp_archive}" "${download_url}"; then echo -e "${RED}错误:下载 Lade CLI 失败。请检查网络连接或 URL 是否正确。${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1; fi 346 | 347 | echo "下载完成,正在解压..." 348 | # 使用 -o 选项覆盖现有文件,-C 解压到指定目录 349 | if [ "${file_extension}" == ".tar.gz" ]; then if ! tar -xzof "${temp_archive}" -C "${lade_temp_dir}"; then echo -e "${RED}错误:解压 .tar.gz 文件失败。${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1; fi 350 | elif [ "${file_extension}" == ".zip" ]; then if ! unzip -o "${temp_archive}" -d "${lade_temp_dir}"; then echo -e "${RED}错误:解压 .zip 文件失败。${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1; fi 351 | else echo -e "${RED}错误:不支持的压缩文件格式:${file_extension}${NC}"; rm -rf "${lade_temp_dir}" || true; exit 1; fi 352 | 353 | # 假设 lade 可执行文件在解压后的目录内,或者直接在临时目录中 354 | local extracted_lade_path="" 355 | # 尝试在 lade_temp_dir 根目录查找 356 | if [ -f "${lade_temp_dir}/${LADE_CLI_NAME}" ]; then 357 | extracted_lade_path="${lade_temp_dir}/${LADE_CLI_NAME}" 358 | else 359 | # 否则,递归查找 360 | extracted_lade_path=$(find "${lade_temp_dir}" -type f -name "${LADE_CLI_NAME}" -perm +111 2>/dev/null | head -n 1) 361 | fi 362 | 363 | if [ -z "${extracted_lade_path}" ]; then 364 | echo -e "${RED}错误:在解压后的临时目录中未找到 '${LADE_CLI_NAME}' 可执行文件。请检查压缩包内容。${NC}" 365 | rm -rf "${lade_temp_dir}" || true 366 | exit 1 367 | fi 368 | 369 | # 确保找到的文件有执行权限 370 | if [ ! -x "${extracted_lade_path}" ]; then 371 | echo -e "${YELLOW}警告:找到的 '${LADE_CLI_NAME}' 文件没有执行权限,正在添加...${NC}" 372 | chmod +x "${extracted_lade_path}" 373 | fi 374 | 375 | echo "正在将 Lade CLI 移动到 ${LADE_INSTALL_PATH}..." 376 | if ! sudo mv "${extracted_lade_path}" "${LADE_INSTALL_PATH}"; then 377 | echo -e "${RED}错误:移动 Lade CLI 文件失败。可能需要管理员权限或目录不存在。${NC}" 378 | rm -rf "${lade_temp_dir}" || true 379 | exit 1 380 | fi 381 | # 再次确保最终位置的文件有执行权限 382 | sudo chmod +x "${LADE_INSTALL_PATH}" 383 | 384 | echo -e "${GREEN}Lade CLI 已成功下载、解压并安装到 ${LADE_INSTALL_PATH}${NC}" 385 | # 清理临时下载目录 386 | rm -rf "${lade_temp_dir}" || true 387 | return 0 388 | } 389 | 390 | # --- 脚本主流程 --- 391 | display_welcome 392 | 393 | # 尝试安装 Lade CLI,如果失败则退出 394 | install_lade_cli || exit 1 395 | 396 | while true; do 397 | echo "" 398 | echo -e "${CYAN}#############################################################${NC}" 399 | echo -e "${CYAN}#${NC} ${BLUE}Lade 管理主菜单${NC} ${CYAN}#${NC}" 400 | echo -e "${CYAN}#############################################################${NC}" 401 | echo -e "${GREEN}1. ${NC}部署 Ladefree 应用" 402 | echo -e "${GREEN}2. ${NC}查看所有 Lade 应用" 403 | echo -e "${GREEN}3. ${NC}删除 Lade 应用" 404 | echo -e "${GREEN}4. ${NC}查看应用日志" 405 | echo -e "${GREEN}5. ${NC}刷新 Lade 登录状态" 406 | echo -e "${RED}6. ${NC}退出" 407 | echo -e "${CYAN}-------------------------------------------------------------${NC}" 408 | read -p "请选择一个操作 (1-6): " CHOICE 409 | 410 | case "$CHOICE" in 411 | 1) deploy_app ;; 412 | 2) view_apps ;; 413 | 3) delete_app ;; 414 | 4) view_app_logs ;; 415 | 5) ensure_lade_login ;; 416 | 6) echo -e "${CYAN}退出脚本。再见!${NC}"; break ;; 417 | *) echo -e "${RED}无效的选择,请输入 1 到 6 之间的数字。${NC}" ;; 418 | esac 419 | echo "" 420 | read -p "按 Enter 键继续..." 421 | done 422 | 423 | echo -e "${BLUE}脚本执行完毕。${NC}" 424 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export UUID=${UUID:-'5ecc0799-1464-48b1-87c0-bbdeb63823fc'} # 哪吒v1,在不同的平台部署需要改UUID,否则会覆盖 3 | export NEZHA_SERVER=${NEZHA_SERVER:-''} # v1哪吒填写形式:nezha.abc.com:8008,v0哪吒填写形式:nezha.abc.com 4 | export NEZHA_PORT=${NEZHA_PORT:-''} # v1哪吒不要填写这个,v0哪吒agent端口为{443,8443,2053,2083,2087,2096}其中之一时自动开启tls 5 | export NEZHA_KEY=${NEZHA_KEY:-''} # v1的NZ_CLIENT_SECRET或v0的agent密钥 6 | export ARGO_DOMAIN=${ARGO_DOMAIN:-''} # 固定隧道域名,留空即启用临时隧道 7 | export ARGO_AUTH=${ARGO_AUTH:-''} # 固定隧道token或json,留空即启用临时隧道 8 | export CFIP=${CFIP:-'joeyblog.net'} # argo节点优选域名或优选ip 9 | export CFPORT=${CFPORT:-'443'} # argo节点端口 10 | export NAME=${NAME:-'Vls'} # 节点名称 11 | export FILE_PATH=${FILE_PATH:-'./.npm'} # sub 路径 12 | export ARGO_PORT=${ARGO_PORT:-'8001'} # argo端口 使用固定隧道token,cloudflare后台设置的端口需和这里对应 13 | export TUIC_PORT=${TUIC_PORT:-'40000'} # Tuic 端口,支持多端口玩具可填写,否则不动 14 | export HY2_PORT=${HY2_PORT:-'50000'} # Hy2 端口,支持多端口玩具可填写,否则不动 15 | export REALITY_PORT=${REALITY_PORT:-'60000'} # reality 端口,支持多端口玩具可填写,否则不动 16 | export CHAT_ID=${CHAT_ID:-''} # TG chat_id,可在https://t.me/laowang_serv00_bot 获取 17 | export BOT_TOKEN=${BOT_TOKEN:-''} # TG bot_token, 使用自己的bot需要填写,使用上方的bot不用填写,不会给别人发送 18 | export UPLOAD_URL=${UPLOAD_URL:-''} # 订阅自动上传地址,没有可不填,需要填部署Merge-sub项目后的首页地址,例如:https://merge.serv00.net 19 | 20 | echo "ZGVsZXRlX29sZF9ub2RlcygpIHsKICBbWyAteiAkVVBMT0FEX1VSTCB8fCAhIC1mICIke0ZJTEVfUEFUSH0vc3ViLnR4dCIgXV0gJiYgcmV0dXJuCiAgb2xkX25vZGVzPSQoYmFzZTY0IC1kICIke0ZJTEVfUEFUSH0vc3ViLnR4dCIgfCBncmVwIC1FICcodmxlc3N8dm1lc3N8dHJvamFufGh5c3RlcmlhMnx0dWljKTovLycpCiAgW1sgLXogJG9sZF9ub2RlcyBdXSAmJiByZXR1cm4KCiAganNvbl9kYXRhPSd7Im5vZGVzIjogWycKICBmb3Igbm9kZSBpbiAkb2xkX25vZGVzOyBkbwogICAgICBqc29uX2RhdGErPSJcIiRub2RlXCIsIgogIGRvbmUKICBqc29uX2RhdGE9JHtqc29uX2RhdGElLH0gIAogIGpzb25fZGF0YSs9J119JwoKICBjdXJsIC1YIERFTEVURSAiJFVQTE9BRF9VUkwvYXBpL2RlbGV0ZS1ub2RlcyIgXAogICAgICAgIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIFwKICAgICAgICAtZCAiJGpzb25fZGF0YSIgPiAvZGV2L251bGwgMj4mMQp9CmRlbGV0ZV9vbGRfbm9kZXMKClsgISAtZCAiJHtGSUxFX1BBVEh9IiBdICYmIG1rZGlyIC1wICIke0ZJTEVfUEFUSH0iICYmIHJtIC1yZiBib290LmxvZyBjb25maWcuanNvbiB0dW5uZWwuanNvbiB0dW5uZWwueW1sICIke0ZJTEVfUEFUSH0vc3ViLnR4dCIgPi9kZXYvbnVsbCAyPiYxCgoKYXJnb19jb25maWd1cmUoKSB7CiAgaWYgW1sgLXogJEFSR09fQVVUSCB8fCAteiAkQVJHT19ET01BSU4gXV07IHRoZW4KICAgIGVjaG8gLWUgIlxlWzE7MzJtQVJHT19ET01BSU4gb3IgQVJHT19BVVRIIHZhcmlhYmxlIGlzIGVtcHR5LCB1c2UgcXVpY2sgdHVubmVsc1xlWzBtIiAgIAogICAgcmV0dXJuCiAgZmkKCiAgaWYgW1sgJEFSR09fQVVUSCA9fiBUdW5uZWxTZWNyZXQgXV07IHRoZW4KICAgIGVjaG8gJEFSR09fQVVUSCA+IHR1bm5lbC5qc29uCiAgICBjYXQgPiB0dW5uZWwueW1sIDw8IEVPRgp0dW5uZWw6ICQoY3V0IC1kXCIgLWYxMiA8PDwgIiRBUkdPX0FVVEgiKQpjcmVkZW50aWFscy1maWxlOiB0dW5uZWwuanNvbgpwcm90b2NvbDogaHR0cDIKCmluZ3Jlc3M6CiAgLSBob3N0bmFtZTogJEFSR09fRE9NQUlOCiAgICBzZXJ2aWNlOiBodHRwOi8vbG9jYWxob3N0OiRBUkdPX1BPUlQKICAgIG9yaWdpblJlcXVlc3Q6CiAgICAgIG5vVExTVmVyaWZ5OiB0cnVlCiAgLSBzZXJ2aWNlOiBodHRwX3N0YXR1czo0MDQKRU9GCiAgZWxzZQogICAgZWNobyAtZSAiXGVbMTszMm1BUkdPX0FVVEggbWlzbWF0Y2ggVHVubmVsU2VjcmV0LHVzZSB0b2tlbiBjb25uZWN0IHRvIHR1bm5lbFxlWzBtIgogIGZpCn0KYXJnb19jb25maWd1cmUKd2FpdAoKZG93bmxvYWRfYW5kX3J1bigpIHsKQVJDSD0kKHVuYW1lIC1tKSAmJiBET1dOTE9BRF9ESVI9Ii4iICYmIG1rZGlyIC1wICIkRE9XTkxPQURfRElSIiAmJiBGSUxFX0lORk89KCkKaWYgWyAiJEFSQ0giID09ICJhcm0iIF0gfHwgWyAiJEFSQ0giID09ICJhcm02NCIgXSB8fCBbICIkQVJDSCIgPT0gImFhcmNoNjQiIF07IHRoZW4KICAgIEJBU0VfVVJMPSJodHRwczovL2FybTY0LnNzc3MubnljLm1uIgplbGlmIFsgIiRBUkNIIiA9PSAiYW1kNjQiIF0gfHwgWyAiJEFSQ0giID09ICJ4ODZfNjQiIF0gfHwgWyAiJEFSQ0giID09ICJ4ODYiIF07IHRoZW4KICAgIEJBU0VfVVJMPSJodHRwczovL2FtZDY0LnNzc3MubnljLm1uIgplbHNlCiAgICBlY2hvICJVbnN1cHBvcnRlZCBhcmNoaXRlY3R1cmU6ICRBUkNIIgogICAgZXhpdCAxCmZpCkZJTEVfSU5GTz0oIiRCQVNFX1VSTC9zYiB3ZWIiICIkQkFTRV9VUkwvYm90IGJvdCIpCgppZiBbIC1uICIkTkVaSEFfUE9SVCIgXTsgdGhlbgogICAgRklMRV9JTkZPKz0oIiRCQVNFX1VSTC9hZ2VudCBucG0iKQplbHNlCiAgICBGSUxFX0lORk8rPSgiJEJBU0VfVVJML3YxIHBocCIpCiAgICBjYXQgPiAiJHtGSUxFX1BBVEh9L2NvbmZpZy55YW1sIiA8PCBFT0YKY2xpZW50X3NlY3JldDogJHtORVpIQV9LRVl9CmRlYnVnOiBmYWxzZQpkaXNhYmxlX2F1dG9fdXBkYXRlOiB0cnVlCmRpc2FibGVfY29tbWFuZF9leGVjdXRlOiBmYWxzZQpkaXNhYmxlX2ZvcmNlX3VwZGF0ZTogdHJ1ZQpkaXNhYmxlX25hdDogZmFsc2UKZGlzYWJsZV9zZW5kX3F1ZXJ5OiBmYWxzZQpncHU6IGZhbHNlCmluc2VjdXJlX3RsczogZmFsc2UKaXBfcmVwb3J0X3BlcmlvZDogMTgwMApyZXBvcnRfZGVsYXk6IDEKc2VydmVyOiAke05FWkhBX1NFUlZFUn0Kc2tpcF9jb25uZWN0aW9uX2NvdW50OiBmYWxzZQpza2lwX3Byb2NzX2NvdW50OiBmYWxzZQp0ZW1wZXJhdHVyZTogZmFsc2UKdGxzOiBmYWxzZQp1c2VfZ2l0ZWVfdG9fdXBncmFkZTogZmFsc2UKdXNlX2lwdjZfY291bnRyeV9jb2RlOiBmYWxzZQp1dWlkOiAke1VVSUR9CkVPRgpmaQpkZWNsYXJlIC1BIEZJTEVfTUFQCmdlbmVyYXRlX3JhbmRvbV9uYW1lKCkgewogICAgbG9jYWwgY2hhcnM9YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkwCiAgICBsb2NhbCBuYW1lPSIiCiAgICBmb3IgaSBpbiB7MS4uNn07IGRvCiAgICAgICAgbmFtZT0iJG5hbWUke2NoYXJzOlJBTkRPTSUkeyNjaGFyc306MX0iCiAgICBkb25lCiAgICBlY2hvICIkbmFtZSIKfQpkb3dubG9hZF9maWxlKCkgewogICAgbG9jYWwgVVJMPSQxCiAgICBsb2NhbCBORVdfRklMRU5BTUU9JDIKCiAgICBpZiBjb21tYW5kIC12IGN1cmwgPi9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAgICAgY3VybCAtTCAtc1MgLW8gIiRORVdfRklMRU5BTUUiICIkVVJMIgogICAgICAgIGVjaG8gLWUgIlxlWzE7MzJtRG93bmxvYWRlZCAkTkVXX0ZJTEVOQU1FIGJ5IGN1cmxcZVswbSIKICAgIGVsaWYgY29tbWFuZCAtdiB3Z2V0ID4vZGV2L251bGwgMj4mMTsgdGhlbgogICAgICAgIHdnZXQgLXEgLU8gIiRORVdfRklMRU5BTUUiICIkVVJMIgogICAgICAgIGVjaG8gLWUgIlxlWzE7MzJtRG93bmxvYWRlZCAkTkVXX0ZJTEVOQU1FIGJ5IHdnZXRcZVswbSIKICAgIGVsc2UKICAgICAgICBlY2hvIC1lICJcZVsxOzMzbU5laXRoZXIgY3VybCBub3Igd2dldCBpcyBhdmFpbGFibGUgZm9yIGRvd25sb2FkaW5nXGVbMG0iCiAgICAgICAgZXhpdCAxCiAgICBmaQp9CmZvciBlbnRyeSBpbiAiJHtGSUxFX0lORk9bQF19IjsgZG8KICAgIFVSTD0kKGVjaG8gIiRlbnRyeSIgfCBjdXQgLWQgJyAnIC1mIDEpCiAgICBSQU5ET01fTkFNRT0kKGdlbmVyYXRlX3JhbmRvbV9uYW1lKQogICAgTkVXX0ZJTEVOQU1FPSIkRE9XTkxPQURfRElSLyRSQU5ET01fTkFNRSIKICAgIAogICAgZG93bmxvYWRfZmlsZSAiJFVSTCIgIiRORVdfRklMRU5BTUUiCiAgICAKICAgIGNobW9kICt4ICIkTkVXX0ZJTEVOQU1FIgogICAgRklMRV9NQVBbJChlY2hvICIkZW50cnkiIHwgY3V0IC1kICcgJyAtZiAyKV09IiRORVdfRklMRU5BTUUiCmRvbmUKd2FpdAoKb3V0cHV0PSQoLi8iJChiYXNlbmFtZSAke0ZJTEVfTUFQW3dlYl19KSIgZ2VuZXJhdGUgcmVhbGl0eS1rZXlwYWlyKQpwcml2YXRlX2tleT0kKGVjaG8gIiR7b3V0cHV0fSIgfCBhd2sgJy9Qcml2YXRlS2V5Oi8ge3ByaW50ICQyfScpCnB1YmxpY19rZXk9JChlY2hvICIke291dHB1dH0iIHwgYXdrICcvUHVibGljS2V5Oi8ge3ByaW50ICQyfScpCgpvcGVuc3NsIGVjcGFyYW0gLWdlbmtleSAtbmFtZSBwcmltZTI1NnYxIC1vdXQgInByaXZhdGUua2V5IgpvcGVuc3NsIHJlcSAtbmV3IC14NTA5IC1kYXlzIDM2NTAgLWtleSAicHJpdmF0ZS5rZXkiIC1vdXQgImNlcnQucGVtIiAtc3ViaiAiL0NOPWJpbmcuY29tIgoKICBjYXQgPiBjb25maWcuanNvbiA8PCBFT0YKewogICAgImxvZyI6IHsKICAgICAgICAiZGlzYWJsZWQiOiB0cnVlLAogICAgICAgICJsZXZlbCI6ICJpbmZvIiwKICAgICAgICAidGltZXN0YW1wIjogdHJ1ZQogICAgfSwKICAgICJkbnMiOiB7CiAgICAgICAgInNlcnZlcnMiOiBbCiAgICAgICAgewogICAgICAgICAgInRhZyI6ICJnb29nbGUiLAogICAgICAgICAgImFkZHJlc3MiOiAidGxzOi8vOC44LjguOCIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAiaW5ib3VuZHMiOiBbCiAgICB7CiAgICAgICJ0YWciOiAidm1lc3Mtd3MtaW4iLAogICAgICAidHlwZSI6ICJ2bWVzcyIsCiAgICAgICJsaXN0ZW4iOiAiOjoiLAogICAgICAibGlzdGVuX3BvcnQiOiAke0FSR09fUE9SVH0sCiAgICAgICAgInVzZXJzIjogWwogICAgICAgIHsKICAgICAgICAgICJ1dWlkIjogIiR7VVVJRH0iCiAgICAgICAgfQogICAgICBdLAogICAgICAidHJhbnNwb3J0IjogewogICAgICAgICJ0eXBlIjogIndzIiwKICAgICAgICAicGF0aCI6ICIvdm1lc3MtYXJnbyIsCiAgICAgICAgImVhcmx5X2RhdGFfaGVhZGVyX25hbWUiOiAiU2VjLVdlYlNvY2tldC1Qcm90b2NvbCIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgInRhZyI6ICJ0dWljLWluIiwKICAgICAgInR5cGUiOiAidHVpYyIsCiAgICAgICJsaXN0ZW4iOiAiOjoiLAogICAgICAibGlzdGVuX3BvcnQiOiAke1RVSUNfUE9SVH0sCiAgICAgICJ1c2VycyI6IFsKICAgICAgICB7CiAgICAgICAgICAidXVpZCI6ICIke1VVSUR9IiwKICAgICAgICAgICJwYXNzd29yZCI6ICJhZG1pbiIKICAgICAgICB9CiAgICAgIF0sCiAgICAgICJjb25nZXN0aW9uX2NvbnRyb2wiOiAiYmJyIiwKICAgICAgInRscyI6IHsKICAgICAgICAiZW5hYmxlZCI6IHRydWUsCiAgICAgICAgImFscG4iOiBbCiAgICAgICAgICAiaDMiCiAgICAgICAgXSwKICAgICAgICAiY2VydGlmaWNhdGVfcGF0aCI6ICJjZXJ0LnBlbSIsCiAgICAgICAgImtleV9wYXRoIjogInByaXZhdGUua2V5IgogICAgICB9CiAgICB9LAogICAgewogICAgICAidGFnIjogImh5c3RlcmlhMi1pbiIsCiAgICAgICJ0eXBlIjogImh5c3RlcmlhMiIsCiAgICAgICJsaXN0ZW4iOiAiOjoiLAogICAgICAibGlzdGVuX3BvcnQiOiAke0hZMl9QT1JUfSwKICAgICAgICAidXNlcnMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICAicGFzc3dvcmQiOiAiJHtVVUlEfSIKICAgICAgICAgIH0KICAgICAgXSwKICAgICAgIm1hc3F1ZXJhZGUiOiAiaHR0cHM6Ly9iaW5nLmNvbSIsCiAgICAgICAgInRscyI6IHsKICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVlLAogICAgICAgICAgICAiYWxwbiI6IFsKICAgICAgICAgICAgICAgICJoMyIKICAgICAgICAgICAgXSwKICAgICAgICAgICAgImNlcnRpZmljYXRlX3BhdGgiOiAiY2VydC5wZW0iLAogICAgICAgICAgICAia2V5X3BhdGgiOiAicHJpdmF0ZS5rZXkiCiAgICAgICAgICB9CiAgICAgIH0sCiAgICAgIHsKICAgICAgICAidGFnIjogInZsZXNzLXJlYWxpdHktdmVzaW9uIiwKICAgICAgICAidHlwZSI6ICJ2bGVzcyIsCiAgICAgICAgImxpc3RlbiI6ICI6OiIsCiAgICAgICAgImxpc3Rlbl9wb3J0IjogJFJFQUxJVFlfUE9SVCwKICAgICAgICAgICJ1c2VycyI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAidXVpZCI6ICIkVVVJRCIsCiAgICAgICAgICAgICAgICAiZmxvdyI6ICJ4dGxzLXJwcngtdmlzaW9uIgogICAgICAgICAgICAgIH0KICAgICAgICAgIF0sCiAgICAgICAgICAidGxzIjogewogICAgICAgICAgICAgICJlbmFibGVkIjogdHJ1ZSwKICAgICAgICAgICAgICAic2VydmVyX25hbWUiOiAid3d3Lm5hemh1bWkuY29tIiwKICAgICAgICAgICAgICAicmVhbGl0eSI6IHsKICAgICAgICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVlLAogICAgICAgICAgICAgICAgICAiaGFuZHNoYWtlIjogewogICAgICAgICAgICAgICAgICAgICAgInNlcnZlciI6ICJ3d3cubmF6aHVtaS5jb20iLAogICAgICAgICAgICAgICAgICAgICAgInNlcnZlcl9wb3J0IjogNDQzCiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICJwcml2YXRlX2tleSI6ICIkcHJpdmF0ZV9rZXkiLAogICAgICAgICAgICAgICAgICAic2hvcnRfaWQiOiBbCiAgICAgICAgICAgICAgICAgICAgIiIKICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgfQogICBdLAogICJvdXRib3VuZHMiOiBbCiAgICB7CiAgICAgICJ0eXBlIjogImRpcmVjdCIsCiAgICAgICJ0YWciOiAiZGlyZWN0IgogICAgfSwKICAgIHsKICAgICAgInR5cGUiOiAiYmxvY2siLAogICAgICAidGFnIjogImJsb2NrIgogICAgfSwKICAgIHsKICAgICAgInR5cGUiOiAid2lyZWd1YXJkIiwKICAgICAgInRhZyI6ICJ3aXJlZ3VhcmQtb3V0IiwKICAgICAgInNlcnZlciI6ICIxNjIuMTU5LjE5Mi4yMDAiLAogICAgICAic2VydmVyX3BvcnQiOiA0NTAwLAogICAgICAibG9jYWxfYWRkcmVzcyI6IFsKICAgICAgICAiMTcyLjE2LjAuMi8zMiIsCiAgICAgICAgIjI2MDY6NDcwMDoxMTA6OGY3NzoxY2E5OmYwODY6ODQ2Yzo1ZjllLzEyOCIKICAgICAgXSwKICAgICAgInByaXZhdGVfa2V5IjogIndJeHN6ZFIybk1kQTdhMlVsM1hRY25pU2ZTWnFkcWpQYjZ3Nm9wdmY1QVU9IiwKICAgICAgInBlZXJfcHVibGljX2tleSI6ICJibVhPQytGMUZ4RU1GOWR5aUsySDUvMVNVdHpIMEp1Vm81MWgyd1BmZ3lvPSIsCiAgICAgICJyZXNlcnZlZCI6IFsxMjYsIDI0NiwgMTczXQogICAgfQogIF0sCiAgInJvdXRlIjogewogICAgInJ1bGVfc2V0IjogWwogICAgICB7CiAgICAgICAgInRhZyI6ICJuZXRmbGl4IiwKICAgICAgICAidHlwZSI6ICJyZW1vdGUiLAogICAgICAgICJmb3JtYXQiOiAiYmluYXJ5IiwKICAgICAgICAidXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9NZXRhQ3ViZVgvbWV0YS1ydWxlcy1kYXQvc2luZy9nZW8vZ2Vvc2l0ZS9uZXRmbGl4LnNycyIsCiAgICAgICAgImRvd25sb2FkX2RldG91ciI6ICJkaXJlY3QiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAidGFnIjogIm9wZW5haSIsCiAgICAgICAgInR5cGUiOiAicmVtb3RlIiwKICAgICAgICAiZm9ybWF0IjogImJpbmFyeSIsCiAgICAgICAgInVybCI6ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vTWV0YUN1YmVYL21ldGEtcnVsZXMtZGF0L3NpbmcvZ2VvL2dlb3NpdGUvb3BlbmFpLnNycyIsCiAgICAgICAgImRvd25sb2FkX2RldG91ciI6ICJkaXJlY3QiCiAgICAgIH0KICAgIF0sCiAgICAicnVsZXMiOiBbCiAgICAgIHsKICAgICAgICAicnVsZV9zZXQiOiBbIm5ldGZsaXgiLCAib3BlbmFpIl0sCiAgICAgICAgIm91dGJvdW5kIjogIndpcmVndWFyZC1vdXQiCiAgICAgIH0KICAgIF0sCiAgICAiZmluYWwiOiAiZGlyZWN0IgogIH0KfQpFT0YKCmlmIFsgLWUgIiQoYmFzZW5hbWUgJHtGSUxFX01BUFt3ZWJdfSkiIF07IHRoZW4KICAgIG5vaHVwIC4vIiQoYmFzZW5hbWUgJHtGSUxFX01BUFt3ZWJdfSkiIHJ1biAtYyBjb25maWcuanNvbiA+L2Rldi9udWxsIDI+JjEgJgogICAgc2xlZXAgMgogICAgZWNobyAtZSAiXGVbMTszMm0kKGJhc2VuYW1lICR7RklMRV9NQVBbd2ViXX0pIGlzIHJ1bm5pbmdcZVswbSIKZmkKCmlmIFsgLWUgIiQoYmFzZW5hbWUgJHtGSUxFX01BUFtib3RdfSkiIF07IHRoZW4KICAgIGlmIFtbICRBUkdPX0FVVEggPX4gXltBLVowLTlhLXo9XXsxMjAsMjUwfSQgXV07IHRoZW4KICAgICAgYXJncz0idHVubmVsIC0tZWRnZS1pcC12ZXJzaW9uIGF1dG8gLS1uby1hdXRvdXBkYXRlIC0tcHJvdG9jb2wgaHR0cDIgcnVuIC0tdG9rZW4gJHtBUkdPX0FVVEh9IgogICAgZWxpZiBbWyAkQVJHT19BVVRIID1+IFR1bm5lbFNlY3JldCBdXTsgdGhlbgogICAgICBhcmdzPSJ0dW5uZWwgLS1lZGdlLWlwLXZlcnNpb24gYXV0byAtLWNvbmZpZyB0dW5uZWwueW1sIHJ1biIKICAgIGVsc2UKICAgICAgYXJncz0idHVubmVsIC0tZWRnZS1pcC12ZXJzaW9uIGF1dG8gLS1uby1hdXRvdXBkYXRlIC0tcHJvdG9jb2wgaHR0cDIgLS1sb2dmaWxlIGJvb3QubG9nIC0tbG9nbGV2ZWwgaW5mbyAtLXVybCBodHRwOi8vbG9jYWxob3N0OiRBUkdPX1BPUlQiCiAgICBmaQogICAgbm9odXAgLi8iJChiYXNlbmFtZSAke0ZJTEVfTUFQW2JvdF19KSIgJGFyZ3MgPi9kZXYvbnVsbCAyPiYxICYKICAgIHNsZWVwIDIKICAgIGVjaG8gLWUgIlxlWzE7MzJtJChiYXNlbmFtZSAke0ZJTEVfTUFQW2JvdF19KSBpcyBydW5uaW5nXGVbMG0iIApmaQoKaWYgWyAtbiAiJE5FWkhBX1NFUlZFUiIgXSAmJiBbIC1uICIkTkVaSEFfUE9SVCIgXSAmJiBbIC1uICIkTkVaSEFfS0VZIiBdOyB0aGVuCiAgICBpZiBbIC1lICIkKGJhc2VuYW1lICR7RklMRV9NQVBbbnBtXX0pIiBdOyB0aGVuCgkgIHRsc1BvcnRzPSgiNDQzIiAiODQ0MyIgIjIwOTYiICIyMDg3IiAiMjA4MyIgIjIwNTMiKQogICAgICBbWyAiJHt0bHNQb3J0c1sqXX0iID1+ICIke05FWkhBX1BPUlR9IiBdXSAmJiBORVpIQV9UTFM9Ii0tdGxzIiB8fCBORVpIQV9UTFM9IiIKICAgICAgZXhwb3J0IFRNUERJUj0kKHB3ZCkKICAgICAgbm9odXAgLi8iJChiYXNlbmFtZSAke0ZJTEVfTUFQW25wbV19KSIgLXMgJHtORVpIQV9TRVJWRVJ9OiR7TkVaSEFfUE9SVH0gLXAgJHtORVpIQV9LRVl9ICR7TkVaSEFfVExTfSA+L2Rldi9udWxsIDI+JjEgJgogICAgICBzbGVlcCAyCiAgICAgIGVjaG8gLWUgIlxlWzE7MzJtJChiYXNlbmFtZSAke0ZJTEVfTUFQW25wbV19KSBpcyBydW5uaW5nXGVbMG0iCiAgICBmaQplbGlmIFsgLW4gIiRORVpIQV9TRVJWRVIiIF0gJiYgWyAtbiAiJE5FWkhBX0tFWSIgXTsgdGhlbgogICAgaWYgWyAtZSAiJChiYXNlbmFtZSAke0ZJTEVfTUFQW3BocF19KSIgXTsgdGhlbgogICAgICBub2h1cCAuLyIkKGJhc2VuYW1lICR7RklMRV9NQVBbcGhwXX0pIiAtYyAiJHtGSUxFX1BBVEh9L2NvbmZpZy55YW1sIiA+L2Rldi9udWxsIDI+JjEgJgogICAgICBlY2hvIC1lICJcZVsxOzMybSQoYmFzZW5hbWUgJHtGSUxFX01BUFtwaHBdfSkgaXMgcnVubmluZ1xlWzBtIgogICAgZmkKZWxzZQogICAgZWNobyAtZSAiXGVbMTszNW1ORVpIQSB2YXJpYWJsZSBpcyBlbXB0eSwgc2tpcHBpbmcgcnVubmluZ1xlWzBtIgpmaQpmb3Iga2V5IGluICIkeyFGSUxFX01BUFtAXX0iOyBkbwogICAgaWYgWyAtZSAiJChiYXNlbmFtZSAke0ZJTEVfTUFQWyRrZXldfSkiIF07IHRoZW4KICAgICAgICBybSAtcmYgIiQoYmFzZW5hbWUgJHtGSUxFX01BUFska2V5XX0pIiA+L2Rldi9udWxsIDI+JjEKICAgIGZpCmRvbmUKfQpkb3dubG9hZF9hbmRfcnVuCgpnZXRfYXJnb2RvbWFpbigpIHsKICBpZiBbWyAtbiAkQVJHT19BVVRIIF1dOyB0aGVuCiAgICBlY2hvICIkQVJHT19ET01BSU4iCiAgZWxzZQogICAgbG9jYWwgcmV0cnk9MAogICAgbG9jYWwgbWF4X3JldHJpZXM9NgogICAgbG9jYWwgYXJnb2RvbWFpbj0iIgogICAgd2hpbGUgW1sgJHJldHJ5IC1sdCAkbWF4X3JldHJpZXMgXV07IGRvCiAgICAgICgocmV0cnkrKykpCiAgICAgIGFyZ29kb21haW49JChzZWQgLW4gJ3N8LipodHRwczovL1woW14vXSp0cnljbG91ZGZsYXJlXC5jb21cKS4qfFwxfHAnIGJvb3QubG9nKQogICAgICBpZiBbWyAtbiAkYXJnb2RvbWFpbiBdXTsgdGhlbgogICAgICAgIGJyZWFrCiAgICAgIGZpCiAgICAgIHNsZWVwIDEKICAgIGRvbmUKICAgIGVjaG8gIiRhcmdvZG9tYWluIgogIGZpCn0KCnNlbmRfdGVsZWdyYW0oKSB7CiAgWyAtZiAiJHtGSUxFX1BBVEh9L3N1Yi50eHQiIF0gfHwgcmV0dXJuCiAgTUVTU0FHRT0kKGNhdCAiJHtGSUxFX1BBVEh9L3N1Yi50eHQiKQogIExPQ0FMX01FU1NBR0U9IioqKiR7TkFNRX3oioLngrnmjqjpgIHpgJrnn6UqKipcblxgXGBcYCR7TUVTU0FHRX1cYFxgXGAiCiAgaWYgWyAtbiAiJHtCT1RfVE9LRU59IiBdICYmIFsgLW4gIiR7Q0hBVF9JRH0iIF07IHRoZW4KICAgIGN1cmwgLXMgLVggUE9TVCAiaHR0cHM6Ly9hcGkudGVsZWdyYW0ub3JnL2JvdCR7Qk9UX1RPS0VOfS9zZW5kTWVzc2FnZSIgXAogICAgICAtZCAiY2hhdF9pZD0ke0NIQVRfSUR9JnRleHQ9JHtMT0NBTF9NRVNTQUdFfSZwYXJzZV9tb2RlPU1hcmtkb3duIiA+IC9kZXYvbnVsbAoKICBlbGlmIFsgLW4gIiR7Q0hBVF9JRH0iIF07IHRoZW4KICAgIGN1cmwgLXMgLVggUE9TVCAiaHR0cDovL2FwaS50Zy5ndnJhbmRlci5ldS5vcmcvYXBpL25vdGlmeSIgXAogICAgICAtSCAiQXV0aG9yaXphdGlvbjogQmVhcmVyIGVKV1JneEM0TGN6bktMaVVpRG9Vc3dAbk1nREJDQ1NVazZJdzBTOVBicyIgXAogICAgICAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uIiBcCiAgICAgIC1kICIkKHByaW50ZiAneyJjaGF0X2lkIjogIiVzIiwgIm1lc3NhZ2UiOiAiJXMifScgIiR7Q0hBVF9JRH0iICIke0xPQ0FMX01FU1NBR0V9IikiID4gL2Rldi9udWxsCiAgZWxzZQogICAgZWNobyAtZSAiXG5cZVsxOzM1bVRHIHZhcmlhYmxlIGlzIGVtcHR5LHNraXBwaW5nIHNlbnRcZVswbSIKICAgIHJldHVybgogIGZpCgogIGlmIFsgJD8gLWVxIDAgXTsgdGhlbgogICAgZWNobyAtZSAiXG5cZVsxOzMybU5vZGVzIHNlbnQgdG8gVEcgc3VjY2Vzc2Z1bGx5XGVbMG0iCiAgZWxzZQogICAgZWNobyAtZSAiXG5cZVsxOzMxbUZhaWxlZCB0byBzZW5kIG5vZGVzIHRvIFRHXGVbMG0iCiAgZmkKfQoKdXBsb2Rfbm9kZXMoKSB7CiAgICBbWyAteiAkVVBMT0FEX1VSTCB8fCAhIC1mICIke0ZJTEVfUEFUSH0vbGlzdC50eHQiIF1dICYmIHJldHVybgogICAgY29udGVudD0kKGNhdCAke0ZJTEVfUEFUSH0vbGlzdC50eHQpCiAgICBub2Rlcz0kKGVjaG8gIiRjb250ZW50IiB8IGdyZXAgLUUgJyh2bGVzc3x2bWVzc3x0cm9qYW58aHlzdGVyaWEyfHR1aWMpOi8vJykKICAgIFtbIC16ICRub2RlcyBdXSAmJiByZXR1cm4KICAgIG5vZGVzPSgkbm9kZXMpCiAgICBqc29uX2RhdGE9J3sibm9kZXMiOiBbJwogICAgZm9yIG5vZGUgaW4gIiR7bm9kZXNbQF19IjsgZG8KICAgICAgICBqc29uX2RhdGErPSJcIiRub2RlXCIsIgogICAgZG9uZQogICAganNvbl9kYXRhPSR7anNvbl9kYXRhJSx9CiAgICBqc29uX2RhdGErPSddfScKCiAgICBjdXJsIC1YIFBPU1QgIiRVUExPQURfVVJML2FwaS9hZGQtbm9kZXMiIFwKICAgICAgICAgLUggIkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbiIgXAogICAgICAgICAtZCAiJGpzb25fZGF0YSIgPiAvZGV2L251bGwgMj4mMQoKICAgIGlmIFtbICQ/IC1lcSAwIF1dOyB0aGVuCiAgICAgICAgZWNobyAtZSAiXDAzM1sxOzMybU5vZGVzIHVwbG9hZGVkIHN1Y2Nlc3NmdWxseVwwMzNbMG0iCiAgICBlbHNlCiAgICAgICAgZWNobyAtZSAiXDAzM1sxOzMxbUZhaWxlZCB0byB1cGxvYWQgbm9kZXNcMDMzWzBtIgogICAgZmkKfQoKYXJnb2RvbWFpbj0kKGdldF9hcmdvZG9tYWluKQplY2hvIC1lICJcZVsxOzMybUFyZ29Eb21haW46XGVbMTszNW0ke2FyZ29kb21haW59XGVbMG1cbiIKc2xlZXAgMQpJUD0kKGN1cmwgLXMgLS1tYXgtdGltZSAyIGlwdjQuaXAuc2IgfHwgY3VybCAtcyAtLW1heC10aW1lIDEgYXBpLmlwaWZ5Lm9yZyB8fCB7IGlwdjY9JChjdXJsIC1zIC0tbWF4LXRpbWUgMSBpcHY2LmlwLnNiKTsgZWNobyAiWyRpcHY2XSI7IH0gfHwgZWNobyAiWFhYIikKSVNQPSQoY3VybCAtcyAtLW1heC10aW1lIDIgaHR0cHM6Ly9zcGVlZC5jbG91ZGZsYXJlLmNvbS9tZXRhIHwgYXdrIC1GXCIgJ3twcmludCAkMjYiLSIkMTh9JyB8IHNlZCAtZSAncy8gL18vZycgfHwgZWNobyAiMC4wIikKClZNRVNTPSJ7IFwidlwiOiBcIjJcIiwgXCJwc1wiOiBcIiR7TkFNRX0tJHtJU1B9XCIsIFwiYWRkXCI6IFwiJHtDRklQfVwiLCBcInBvcnRcIjogXCIke0NGUE9SVH1cIiwgXCJpZFwiOiBcIiR7VVVJRH1cIiwgXCJhaWRcIjogXCIwXCIsIFwic2N5XCI6IFwibm9uZVwiLCBcIm5ldFwiOiBcIndzXCIsIFwidHlwZVwiOiBcIm5vbmVcIiwgXCJob3N0XCI6IFwiJHthcmdvZG9tYWlufVwiLCBcInBhdGhcIjogXCIvdm1lc3MtYXJnbz9lZD0yMDQ4XCIsIFwidGxzXCI6IFwidGxzXCIsIFwic25pXCI6IFwiJHthcmdvZG9tYWlufVwiLCBcImFscG5cIjogXCJcIiwgXCJmcFwiOiBcIlwifSIKCmNhdCA+ICR7RklMRV9QQVRIfS9saXN0LnR4dCA8PEVPRgp2bWVzczovLyQoZWNobyAiJFZNRVNTIiB8IGJhc2U2NCAtdzApCkVPRgoKaWYgWyAiJFRVSUNfUE9SVCIgIT0gIjQwMDAwIiBdOyB0aGVuCiAgZWNobyAtZSAiXG50dWljOi8vJHtVVUlEfTphZG1pbkAke0lQfToke1RVSUNfUE9SVH0/c25pPXd3dy5iaW5nLmNvbSZhbHBuPWgzJmNvbmdlc3Rpb25fY29udHJvbD1iYnIjJHtOQU1FfS0ke0lTUH0iID4+ICR7RklMRV9QQVRIfS9saXN0LnR4dApmaQoKaWYgWyAiJEhZMl9QT1JUIiAhPSAiNTAwMDAiIF07IHRoZW4KICBlY2hvIC1lICJcbmh5c3RlcmlhMjovLyR7VVVJRH1AJHtJUH06JHtIWTJfUE9SVH0vP3NuaT13d3cuYmluZy5jb20mYWxwbj1oMyZpbnNlY3VyZT0xIyR7TkFNRX0tJHtJU1B9IiA+PiAke0ZJTEVfUEFUSH0vbGlzdC50eHQKZmkKCmlmIFsgIiRSRUFMSVRZX1BPUlQiICE9ICI2MDAwMCIgXTsgdGhlbgogIGVjaG8gLWUgIlxudmxlc3M6Ly8ke1VVSUR9QCR7SVB9OiR7UkVBTElUWV9QT1JUfT9lbmNyeXB0aW9uPW5vbmUmZmxvdz14dGxzLXJwcngtdmlzaW9uJnNlY3VyaXR5PXJlYWxpdHkmc25pPXd3dy5uYXpodW1pLmNvbSZmcD1jaHJvbWUmcGJrPSR7cHVibGljX2tleX0mdHlwZT10Y3AmaGVhZGVyVHlwZT1ub25lIyR7TkFNRX0tJHtJU1B9IiA+PiAke0ZJTEVfUEFUSH0vbGlzdC50eHQKZmkKCmJhc2U2NCAtdzAgJHtGSUxFX1BBVEh9L2xpc3QudHh0ID4gJHtGSUxFX1BBVEh9L3N1Yi50eHQKY2F0ICR7RklMRV9QQVRIfS9zdWIudHh0CmVjaG8gLWUgIlxuXG5cZVsxOzMybSR7RklMRV9QQVRIfS9zdWIudHh0IHNhdmVkIHN1Y2Nlc3NmdWxseVxlWzBtIgp1cGxvZF9ub2RlcwpzZW5kX3RlbGVncmFtCmVjaG8gLWUgIlxuXGVbMTszMm1SdW5uaW5nIGRvbmUhXGVbMG1cbiIKc2xlZXAgMTAgCgpybSAtcmYgYm9vdC5sb2cgY29uZmlnLmpzb24gc2IubG9nIGNvcmUgZmFrZV91c2VyYWdlbnRfMC4yLjAuanNvbiAke0ZJTEVfUEFUSH0vbGlzdC50eHQgPi9kZXYvbnVsbCAyPiYx" | base64 -d | bash 21 | -------------------------------------------------------------------------------- /q.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RED='\033[0;31m' 4 | GREEN='\033[0;32m' 5 | YELLOW='\033[1;33m' 6 | BLUE='\033[0;34m' 7 | NC='\033[0m' 8 | 9 | NODE_INFO_FILE="$HOME/.xray_nodes_info" 10 | PROJECT_DIR_NAME="python-xray-argo" 11 | 12 | if [ "$1" = "-v" ]; then 13 | if [ -f "$NODE_INFO_FILE" ]; then 14 | echo -e "${GREEN}========================================${NC}" 15 | echo -e "${GREEN} 节点信息查看 ${NC}" 16 | echo -e "${GREEN}========================================${NC}" 17 | echo 18 | cat "$NODE_INFO_FILE" 19 | echo 20 | else 21 | echo -e "${RED}未找到节点信息文件${NC}" 22 | echo -e "${YELLOW}请先运行部署脚本生成节点信息${NC}" 23 | fi 24 | exit 0 25 | fi 26 | 27 | generate_uuid() { 28 | if command -v uuidgen &> /dev/null; then 29 | uuidgen | tr '[:upper:]' '[:lower:]' 30 | elif command -v python3 &> /dev/null; then 31 | python3 -c "import uuid; print(str(uuid.uuid4()))" 32 | else 33 | hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/urandom | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-\9\10-\11\12\13\14\15\16/' | tr '[:upper:]' '[:lower:]' 34 | fi 35 | } 36 | 37 | clear 38 | 39 | echo -e "${GREEN}========================================${NC}" 40 | echo -e "${GREEN} Python Xray Argo 一键部署脚本 (优化版) ${NC}" 41 | echo -e "${GREEN}========================================${NC}" 42 | echo 43 | echo -e "${BLUE}基于项目: ${YELLOW}https://github.com/eooce/python-xray-argo${NC}" 44 | echo -e "${BLUE}脚本仓库: ${YELLOW}https://github.com/byJoey/free-vps-py${NC}" 45 | echo -e "${BLUE}TG交流群: ${YELLOW}https://t.me/+ft-zI76oovgwNmRh${NC}" 46 | echo -e "${RED}脚本作者YouTube: ${YELLOW}https://www.youtube.com/@joeyblog${NC}" 47 | echo 48 | echo -e "${GREEN}本脚本基于 eooce 大佬的项目进行修改和增强${NC}" 49 | echo -e "${GREEN}提供极速和完整两种配置模式,简化部署流程${NC}" 50 | echo -e "${GREEN}核心分流规则已升级为 Geosite,覆盖更全面${NC}" 51 | echo -e "${GREEN}支持自动UUID生成、后台运行、节点信息输出${NC}" 52 | echo 53 | 54 | echo -e "${YELLOW}请选择操作:${NC}" 55 | echo -e "${BLUE}1) 极速模式 - 只修改UUID并启动${NC}" 56 | echo -e "${BLUE}2) 完整模式 - 详细配置所有选项${NC}" 57 | echo -e "${BLUE}3) 查看节点信息 - 显示已保存的节点信息${NC}" 58 | echo -e "${BLUE}4) 查看保活状态 - 检查Hugging Face API保活状态${NC}" 59 | echo 60 | read -p "请输入选择 (1/2/3/4): " MODE_CHOICE 61 | 62 | if [ "$MODE_CHOICE" = "3" ]; then 63 | if [ -f "$NODE_INFO_FILE" ]; then 64 | echo 65 | echo -e "${GREEN}========================================${NC}" 66 | echo -e "${GREEN} 节点信息查看 ${NC}" 67 | echo -e "${GREEN}========================================${NC}" 68 | echo 69 | cat "$NODE_INFO_FILE" 70 | echo 71 | echo -e "${YELLOW}提示: 如需重新部署,请重新运行脚本选择模式1或2${NC}" 72 | else 73 | echo 74 | echo -e "${RED}未找到节点信息文件${NC}" 75 | echo -e "${YELLOW}请先运行部署脚本生成节点信息${NC}" 76 | echo 77 | echo -e "${BLUE}是否现在开始部署? (y/n)${NC}" 78 | read -p "> " START_DEPLOY 79 | if [ "$START_DEPLOY" = "y" ] || [ "$START_DEPLOY" = "Y" ]; then 80 | echo -e "${YELLOW}请选择部署模式:${NC}" 81 | echo -e "${BLUE}1) 极速模式${NC}" 82 | echo -e "${BLUE}2) 完整模式${NC}" 83 | read -p "请输入选择 (1/2): " MODE_CHOICE 84 | else 85 | echo -e "${GREEN}退出脚本${NC}" 86 | exit 0 87 | fi 88 | fi 89 | 90 | if [ "$MODE_CHOICE" != "1" ] && [ "$MODE_CHOICE" != "2" ]; then 91 | echo -e "${GREEN}退出脚本${NC}" 92 | exit 0 93 | fi 94 | fi 95 | 96 | if [ "$MODE_CHOICE" = "4" ]; then 97 | echo 98 | echo -e "${GREEN}========================================${NC}" 99 | echo -e "${GREEN} Hugging Face API 保活状态检查 ${NC}" 100 | echo -e "${GREEN}========================================${NC}" 101 | echo 102 | 103 | if [ -d "$PROJECT_DIR_NAME" ]; then 104 | cd "$PROJECT_DIR_NAME" 105 | fi 106 | 107 | KEEPALIVE_PID=$(pgrep -f "keep_alive_task.sh") 108 | 109 | if [ -n "$KEEPALIVE_PID" ]; then 110 | echo -e "服务状态: ${GREEN}运行中${NC}" 111 | echo -e "进程PID: ${BLUE}$KEEPALIVE_PID${NC}" 112 | if [ -f "keep_alive_task.sh" ]; then 113 | REPO_ID=$(grep 'huggingface.co/api/spaces/' keep_alive_task.sh | head -1 | sed -n 's|.*api/spaces/\([^"]*\).*|\1|p') 114 | echo -e "目标仓库: ${YELLOW}$REPO_ID (类型: Space)${NC}" 115 | fi 116 | 117 | echo -e "\n${YELLOW}--- 最近一次保活状态 ---${NC}" 118 | if [ -f "keep_alive_status.log" ]; then 119 | cat keep_alive_status.log 120 | else 121 | echo -e "${YELLOW}尚未生成状态日志,请稍等片刻(最多2分钟)后重试...${NC}" 122 | fi 123 | else 124 | echo -e "服务状态: ${RED}未运行${NC}" 125 | echo -e "${YELLOW}提示: 您可能尚未部署服务或未在部署时设置Hugging Face保活。${NC}" 126 | fi 127 | echo 128 | exit 0 129 | fi 130 | 131 | echo -e "${BLUE}检查并安装依赖...${NC}" 132 | if ! command -v python3 &> /dev/null; then 133 | echo -e "${YELLOW}正在安装 Python3...${NC}" 134 | sudo apt-get update && sudo apt-get install -y python3 python3-pip 135 | fi 136 | 137 | if ! python3 -c "import requests" &> /dev/null; then 138 | echo -e "${YELLOW}正在安装 Python 依赖: requests...${NC}" 139 | pip3 install requests 140 | fi 141 | 142 | if [ ! -d "$PROJECT_DIR_NAME" ]; then 143 | echo -e "${BLUE}下载完整仓库...${NC}" 144 | if command -v git &> /dev/null; then 145 | git clone https://github.com/eooce/python-xray-argo.git "$PROJECT_DIR_NAME" 146 | else 147 | echo -e "${YELLOW}Git未安装,使用wget下载...${NC}" 148 | wget -q https://github.com/eooce/python-xray-argo/archive/refs/heads/main.zip -O python-xray-argo.zip 149 | if command -v unzip &> /dev/null; then 150 | unzip -q python-xray-argo.zip 151 | mv python-xray-argo-main "$PROJECT_DIR_NAME" 152 | rm python-xray-argo.zip 153 | else 154 | echo -e "${YELLOW}正在安装 unzip...${NC}" 155 | sudo apt-get install -y unzip 156 | unzip -q python-xray-argo.zip 157 | mv python-xray-argo-main "$PROJECT_DIR_NAME" 158 | rm python-xray-argo.zip 159 | fi 160 | fi 161 | 162 | if [ $? -ne 0 ] || [ ! -d "$PROJECT_DIR_NAME" ]; then 163 | echo -e "${RED}下载失败,请检查网络连接${NC}" 164 | exit 1 165 | fi 166 | fi 167 | 168 | cd "$PROJECT_DIR_NAME" 169 | 170 | echo -e "${GREEN}依赖安装完成!${NC}" 171 | echo 172 | 173 | if [ ! -f "app.py" ]; then 174 | echo -e "${RED}未找到app.py文件!${NC}" 175 | exit 1 176 | fi 177 | 178 | cp app.py app.py.backup 179 | echo -e "${YELLOW}已备份原始文件为 app.py.backup${NC}" 180 | 181 | KEEP_ALIVE_HF="false" 182 | HF_TOKEN="" 183 | HF_REPO_ID="" 184 | 185 | configure_hf_keep_alive() { 186 | echo 187 | echo -e "${YELLOW}是否设置 Hugging Face API 自动保活? (y/n)${NC}" 188 | read -p "> " SETUP_KEEP_ALIVE 189 | if [ "$SETUP_KEEP_ALIVE" = "y" ] || [ "$SETUP_KEEP_ALIVE" = "Y" ]; then 190 | echo -e "${YELLOW}请输入您的 Hugging Face 访问令牌 (Token):${NC}" 191 | echo -e "${BLUE}(令牌用于API认证,输入时将不可见。请前往 https://huggingface.co/settings/tokens 获取)${NC}" 192 | read -sp "Token: " HF_TOKEN_INPUT 193 | echo 194 | if [ -z "$HF_TOKEN_INPUT" ]; then 195 | echo -e "${RED}错误:Token 不能为空。已取消保活设置。${NC}" 196 | return 197 | fi 198 | 199 | echo -e "${YELLOW}请输入要访问的 Hugging Face 仓库ID (模型或Space均可,例如: joeyhuangt/aaaa):${NC}" 200 | read -p "Repo ID: " HF_REPO_ID_INPUT 201 | if [ -z "$HF_REPO_ID_INPUT" ]; then 202 | echo -e "${RED}错误:仓库ID 不能为空。已取消保活设置。${NC}" 203 | return 204 | fi 205 | 206 | HF_TOKEN="$HF_TOKEN_INPUT" 207 | HF_REPO_ID="$HF_REPO_ID_INPUT" 208 | KEEP_ALIVE_HF="true" 209 | echo -e "${GREEN}Hugging Face API 保活已设置!${NC}" 210 | echo -e "${GREEN}目标仓库: $HF_REPO_ID${NC}" 211 | fi 212 | } 213 | 214 | if [ "$MODE_CHOICE" = "1" ]; then 215 | echo -e "${BLUE}=== 极速模式 ===${NC}" 216 | echo 217 | 218 | echo -e "${YELLOW}当前UUID: $(grep "UUID = " app.py | head -1 | cut -d"'" -f2)${NC}" 219 | read -p "请输入新的 UUID (留空自动生成): " UUID_INPUT 220 | if [ -z "$UUID_INPUT" ]; then 221 | UUID_INPUT=$(generate_uuid) 222 | echo -e "${GREEN}自动生成UUID: $UUID_INPUT${NC}" 223 | fi 224 | 225 | sed -i "s/UUID = os.environ.get('UUID', '[^']*')/UUID = os.environ.get('UUID', '$UUID_INPUT')/" app.py 226 | echo -e "${GREEN}UUID 已设置为: $UUID_INPUT${NC}" 227 | 228 | sed -i "s/CFIP = os.environ.get('CFIP', '[^']*')/CFIP = os.environ.get('CFIP', 'joeyblog.net')/" app.py 229 | echo -e "${GREEN}优选IP已自动设置为: joeyblog.net${NC}" 230 | 231 | configure_hf_keep_alive 232 | 233 | echo -e "${GREEN}分流规则已自动配置${NC}" 234 | echo 235 | echo -e "${GREEN}极速配置完成!正在启动服务...${NC}" 236 | echo 237 | 238 | else 239 | echo -e "${BLUE}=== 完整配置模式 ===${NC}" 240 | echo 241 | 242 | echo -e "${YELLOW}当前UUID: $(grep "UUID = " app.py | head -1 | cut -d"'" -f2)${NC}" 243 | read -p "请输入新的 UUID (留空自动生成): " UUID_INPUT 244 | if [ -z "$UUID_INPUT" ]; then 245 | UUID_INPUT=$(generate_uuid) 246 | echo -e "${GREEN}自动生成UUID: $UUID_INPUT${NC}" 247 | fi 248 | sed -i "s/UUID = os.environ.get('UUID', '[^']*')/UUID = os.environ.get('UUID', '$UUID_INPUT')/" app.py 249 | echo -e "${GREEN}UUID 已设置为: $UUID_INPUT${NC}" 250 | 251 | echo -e "${YELLOW}当前节点名称: $(grep "NAME = " app.py | head -1 | cut -d"'" -f4)${NC}" 252 | read -p "请输入节点名称 (留空保持不变): " NAME_INPUT 253 | if [ -n "$NAME_INPUT" ]; then 254 | sed -i "s/NAME = os.environ.get('NAME', '[^']*')/NAME = os.environ.get('NAME', '$NAME_INPUT')/" app.py 255 | echo -e "${GREEN}节点名称已设置为: $NAME_INPUT${NC}" 256 | fi 257 | 258 | echo -e "${YELLOW}当前服务端口: $(grep "PORT = int" app.py | grep -o "or [0-9]*" | cut -d" " -f2)${NC}" 259 | read -p "请输入服务端口 (留空保持不变): " PORT_INPUT 260 | if [ -n "$PORT_INPUT" ]; then 261 | sed -i "s/PORT = int(os.environ.get('SERVER_PORT') or os.environ.get('PORT') or [0-9]*)/PORT = int(os.environ.get('SERVER_PORT') or os.environ.get('PORT') or $PORT_INPUT)/" app.py 262 | echo -e "${GREEN}端口已设置为: $PORT_INPUT${NC}" 263 | fi 264 | 265 | echo -e "${YELLOW}当前优选IP: $(grep "CFIP = " app.py | cut -d"'" -f4)${NC}" 266 | read -p "请输入优选IP/域名 (留空使用默认 joeyblog.net): " CFIP_INPUT 267 | if [ -z "$CFIP_INPUT" ]; then 268 | CFIP_INPUT="joeyblog.net" 269 | fi 270 | sed -i "s/CFIP = os.environ.get('CFIP', '[^']*')/CFIP = os.environ.get('CFIP', '$CFIP_INPUT')/" app.py 271 | echo -e "${GREEN}优选IP已设置为: $CFIP_INPUT${NC}" 272 | 273 | echo -e "${YELLOW}当前优选端口: $(grep "CFPORT = " app.py | cut -d"'" -f4)${NC}" 274 | read -p "请输入优选端口 (留空保持不变): " CFPORT_INPUT 275 | if [ -n "$CFPORT_INPUT" ]; then 276 | sed -i "s/CFPORT = int(os.environ.get('CFPORT', '[^']*'))/CFPORT = int(os.environ.get('CFPORT', '$CFPORT_INPUT'))/" app.py 277 | echo -e "${GREEN}优选端口已设置为: $CFPORT_INPUT${NC}" 278 | fi 279 | 280 | echo -e "${YELLOW}当前Argo端口: $(grep "ARGO_PORT = " app.py | cut -d"'" -f4)${NC}" 281 | read -p "请输入 Argo 端口 (留空保持不变): " ARGO_PORT_INPUT 282 | if [ -n "$ARGO_PORT_INPUT" ]; then 283 | sed -i "s/ARGO_PORT = int(os.environ.get('ARGO_PORT', '[^']*'))/ARGO_PORT = int(os.environ.get('ARGO_PORT', '$ARGO_PORT_INPUT'))/" app.py 284 | echo -e "${GREEN}Argo端口已设置为: $ARGO_PORT_INPUT${NC}" 285 | fi 286 | 287 | echo -e "${YELLOW}当前订阅路径: $(grep "SUB_PATH = " app.py | cut -d"'" -f4)${NC}" 288 | read -p "请输入订阅路径 (留空保持不变): " SUB_PATH_INPUT 289 | if [ -n "$SUB_PATH_INPUT" ]; then 290 | sed -i "s/SUB_PATH = os.environ.get('SUB_PATH', '[^']*')/SUB_PATH = os.environ.get('SUB_PATH', '$SUB_PATH_INPUT')/" app.py 291 | echo -e "${GREEN}订阅路径已设置为: $SUB_PATH_INPUT${NC}" 292 | fi 293 | 294 | echo 295 | echo -e "${YELLOW}是否配置高级选项? (y/n)${NC}" 296 | read -p "> " ADVANCED_CONFIG 297 | 298 | if [ "$ADVANCED_CONFIG" = "y" ] || [ "$ADVANCED_CONFIG" = "Y" ]; then 299 | echo -e "${YELLOW}当前上传URL: $(grep "UPLOAD_URL = " app.py | cut -d"'" -f4)${NC}" 300 | read -p "请输入上传URL (留空保持不变): " UPLOAD_URL_INPUT 301 | if [ -n "$UPLOAD_URL_INPUT" ]; then 302 | sed -i "s|UPLOAD_URL = os.environ.get('UPLOAD_URL', '[^']*')|UPLOAD_URL = os.environ.get('UPLOAD_URL', '$UPLOAD_URL_INPUT')|" app.py 303 | echo -e "${GREEN}上传URL已设置${NC}" 304 | fi 305 | 306 | echo -e "${YELLOW}当前项目URL: $(grep "PROJECT_URL = " app.py | cut -d"'" -f4)${NC}" 307 | read -p "请输入项目URL (留空保持不变): " PROJECT_URL_INPUT 308 | if [ -n "$PROJECT_URL_INPUT" ]; then 309 | sed -i "s|PROJECT_URL = os.environ.get('PROJECT_URL', '[^']*')|PROJECT_URL = os.environ.get('PROJECT_URL', '$PROJECT_URL_INPUT')|" app.py 310 | echo -e "${GREEN}项目URL已设置${NC}" 311 | fi 312 | 313 | configure_hf_keep_alive 314 | 315 | echo -e "${YELLOW}当前哪吒服务器: $(grep "NEZHA_SERVER = " app.py | cut -d"'" -f4)${NC}" 316 | read -p "请输入哪吒服务器地址 (留空保持不变): " NEZHA_SERVER_INPUT 317 | if [ -n "$NEZHA_SERVER_INPUT" ]; then 318 | sed -i "s|NEZHA_SERVER = os.environ.get('NEZHA_SERVER', '[^']*')|NEZHA_SERVER = os.environ.get('NEZHA_SERVER', '$NEZHA_SERVER_INPUT')|" app.py 319 | 320 | echo -e "${YELLOW}当前哪吒端口: $(grep "NEZHA_PORT = " app.py | cut -d"'" -f4)${NC}" 321 | read -p "请输入哪吒端口 (v1版本留空): " NEZHA_PORT_INPUT 322 | if [ -n "$NEZHA_PORT_INPUT" ]; then 323 | sed -i "s|NEZHA_PORT = os.environ.get('NEZHA_PORT', '[^']*')|NEZHA_PORT = os.environ.get('NEZHA_PORT', '$NEZHA_PORT_INPUT')|" app.py 324 | fi 325 | 326 | echo -e "${YELLOW}当前哪吒密钥: $(grep "NEZHA_KEY = " app.py | cut -d"'" -f4)${NC}" 327 | read -p "请输入哪吒密钥: " NEZHA_KEY_INPUT 328 | if [ -n "$NEZHA_KEY_INPUT" ]; then 329 | sed -i "s|NEZHA_KEY = os.environ.get('NEZHA_KEY', '[^']*')|NEZHA_KEY = os.environ.get('NEZHA_KEY', '$NEZHA_KEY_INPUT')|" app.py 330 | fi 331 | echo -e "${GREEN}哪吒配置已设置${NC}" 332 | fi 333 | 334 | echo -e "${YELLOW}当前Argo域名: $(grep "ARGO_DOMAIN = " app.py | cut -d"'" -f4)${NC}" 335 | read -p "请输入 Argo 固定隧道域名 (留空保持不变): " ARGO_DOMAIN_INPUT 336 | if [ -n "$ARGO_DOMAIN_INPUT" ]; then 337 | sed -i "s|ARGO_DOMAIN = os.environ.get('ARGO_DOMAIN', '[^']*')|ARGO_DOMAIN = os.environ.get('ARGO_DOMAIN', '$ARGO_DOMAIN_INPUT')|" app.py 338 | 339 | echo -e "${YELLOW}当前Argo密钥: $(grep "ARGO_AUTH = " app.py | cut -d"'" -f4)${NC}" 340 | read -p "请输入 Argo 固定隧道密钥: " ARGO_AUTH_INPUT 341 | if [ -n "$ARGO_AUTH_INPUT" ]; then 342 | sed -i "s|ARGO_AUTH = os.environ.get('ARGO_AUTH', '[^']*')|ARGO_AUTH = os.environ.get('ARGO_AUTH', '$ARGO_AUTH_INPUT')|" app.py 343 | fi 344 | echo -e "${GREEN}Argo固定隧道配置已设置${NC}" 345 | fi 346 | 347 | echo -e "${YELLOW}当前Bot Token: $(grep "BOT_TOKEN = " app.py | cut -d"'" -f4)${NC}" 348 | read -p "请输入 Telegram Bot Token (留空保持不变): " BOT_TOKEN_INPUT 349 | if [ -n "$BOT_TOKEN_INPUT" ]; then 350 | sed -i "s|BOT_TOKEN = os.environ.get('BOT_TOKEN', '[^']*')|BOT_TOKEN = os.environ.get('BOT_TOKEN', '$BOT_TOKEN_INPUT')|" app.py 351 | 352 | echo -e "${YELLOW}当前Chat ID: $(grep "CHAT_ID = " app.py | cut -d"'" -f4)${NC}" 353 | read -p "请输入 Telegram Chat ID: " CHAT_ID_INPUT 354 | if [ -n "$CHAT_ID_INPUT" ]; then 355 | sed -i "s|CHAT_ID = os.environ.get('CHAT_ID', '[^']*')|CHAT_ID = os.environ.get('CHAT_ID', '$CHAT_ID_INPUT')|" app.py 356 | fi 357 | echo -e "${GREEN}Telegram配置已设置${NC}" 358 | fi 359 | fi 360 | 361 | echo -e "${GREEN}分流规则已自动配置${NC}" 362 | echo 363 | echo -e "${GREEN}完整配置完成!${NC}" 364 | fi 365 | 366 | echo -e "${YELLOW}=== 当前配置摘要 ===${NC}" 367 | echo -e "UUID: $(grep "UUID = " app.py | head -1 | cut -d"'" -f2)" 368 | echo -e "节点名称: $(grep "NAME = " app.py | head -1 | cut -d"'" -f4)" 369 | echo -e "服务端口: $(grep "PORT = int" app.py | grep -o "or [0-9]*" | cut -d" " -f2)" 370 | echo -e "优选IP: $(grep "CFIP = " app.py | cut -d"'" -f4)" 371 | echo -e "优选端口: $(grep "CFPORT = " app.py | cut -d"'" -f4)" 372 | echo -e "订阅路径: $(grep "SUB_PATH = " app.py | cut -d"'" -f4)" 373 | if [ "$KEEP_ALIVE_HF" = "true" ]; then 374 | echo -e "保活仓库: $HF_REPO_ID" 375 | fi 376 | echo -e "${YELLOW}========================${NC}" 377 | echo 378 | 379 | echo -e "${BLUE}正在启动服务...${NC}" 380 | echo -e "${YELLOW}当前工作目录:$(pwd)${NC}" 381 | echo 382 | 383 | echo -e "${BLUE}正在应用Geosite分流规则和80端口节点...${NC}" 384 | cat > youtube_patch.py << 'EOF' 385 | import os, base64, json, subprocess, time 386 | 387 | with open('app.py', 'r', encoding='utf-8') as f: 388 | content = f.read() 389 | 390 | old_config = 'config ={"log":{"access":"/dev/null","error":"/dev/null","loglevel":"none",},"inbounds":[{"port":ARGO_PORT ,"protocol":"vless","settings":{"clients":[{"id":UUID ,"flow":"xtls-rprx-vision",},],"decryption":"none","fallbacks":[{"dest":3001 },{"path":"/vless-argo","dest":3002 },{"path":"/vmess-argo","dest":3003 },{"path":"/trojan-argo","dest":3004 },],},"streamSettings":{"network":"tcp",},},{"port":3001 ,"listen":"127.0.0.1","protocol":"vless","settings":{"clients":[{"id":UUID },],"decryption":"none"},"streamSettings":{"network":"ws","security":"none"}},{"port":3002 ,"listen":"127.0.0.1","protocol":"vless","settings":{"clients":[{"id":UUID ,"level":0 }],"decryption":"none"},"streamSettings":{"network":"ws","security":"none","wsSettings":{"path":"/vless-argo"}},"sniffing":{"enabled":True ,"destOverride":["http","tls","quic"],"metadataOnly":False }},{"port":3003 ,"listen":"127.0.0.1","protocol":"vmess","settings":{"clients":[{"id":UUID ,"alterId":0 }]},"streamSettings":{"network":"ws","wsSettings":{"path":"/vmess-argo"}},"sniffing":{"enabled":True ,"destOverride":["http","tls","quic"],"metadataOnly":False }},{"port":3004 ,"listen":"127.0.0.1","protocol":"trojan","settings":{"clients":[{"password":UUID },]},"streamSettings":{"network":"ws","security":"none","wsSettings":{"path":"/trojan-argo"}},"sniffing":{"enabled":True ,"destOverride":["http","tls","quic"],"metadataOnly":False }},],"outbounds":[{"protocol":"freedom","tag": "direct" },{"protocol":"blackhole","tag":"block"}]}' 391 | 392 | new_config = '''config = { 393 | "log": { 394 | "access": "/dev/null", 395 | "error": "/dev/null", 396 | "loglevel": "none" 397 | }, 398 | "inbounds": [ 399 | { 400 | "port": ARGO_PORT, 401 | "protocol": "vless", 402 | "settings": { 403 | "clients": [{"id": UUID, "flow": "xtls-rprx-vision"}], 404 | "decryption": "none", 405 | "fallbacks": [ 406 | {"dest": 3001}, 407 | {"path": "/vless-argo", "dest": 3002}, 408 | {"path": "/vmess-argo", "dest": 3003}, 409 | {"path": "/trojan-argo", "dest": 3004} 410 | ] 411 | }, 412 | "streamSettings": {"network": "tcp"} 413 | }, 414 | { 415 | "port": 3001, 416 | "listen": "127.0.0.1", 417 | "protocol": "vless", 418 | "settings": { 419 | "clients": [{"id": UUID}], 420 | "decryption": "none" 421 | }, 422 | "streamSettings": {"network": "ws", "security": "none"} 423 | }, 424 | { 425 | "port": 3002, 426 | "listen": "127.0.0.1", 427 | "protocol": "vless", 428 | "settings": { 429 | "clients": [{"id": UUID, "level": 0}], 430 | "decryption": "none" 431 | }, 432 | "streamSettings": { 433 | "network": "ws", 434 | "security": "none", 435 | "wsSettings": {"path": "/vless-argo"} 436 | }, 437 | "sniffing": { 438 | "enabled": True, 439 | "destOverride": ["http", "tls", "quic"], 440 | "metadataOnly": False 441 | } 442 | }, 443 | { 444 | "port": 3003, 445 | "listen": "127.0.0.1", 446 | "protocol": "vmess", 447 | "settings": { 448 | "clients": [{"id": UUID, "alterId": 0}] 449 | }, 450 | "streamSettings": { 451 | "network": "ws", 452 | "wsSettings": {"path": "/vmess-argo"} 453 | }, 454 | "sniffing": { 455 | "enabled": True, 456 | "destOverride": ["http", "tls", "quic"], 457 | "metadataOnly": False 458 | } 459 | }, 460 | { 461 | "port": 3004, 462 | "listen": "127.0.0.1", 463 | "protocol": "trojan", 464 | "settings": { 465 | "clients": [{"password": UUID}] 466 | }, 467 | "streamSettings": { 468 | "network": "ws", 469 | "security": "none", 470 | "wsSettings": {"path": "/trojan-argo"} 471 | }, 472 | "sniffing": { 473 | "enabled": True, 474 | "destOverride": ["http", "tls", "quic"], 475 | "metadataOnly": False 476 | } 477 | } 478 | ], 479 | "outbounds": [ 480 | {"protocol": "freedom", "tag": "direct"}, 481 | { 482 | "protocol": "vmess", 483 | "tag": "youtube", 484 | "settings": { 485 | "vnext": [{ 486 | "address": "172.233.171.224", 487 | "port": 16416, 488 | "users": [{ 489 | "id": "8c1b9bea-cb51-43bb-a65c-0af31bbbf145", 490 | "alterId": 0 491 | }] 492 | }] 493 | }, 494 | "streamSettings": {"network": "tcp"} 495 | }, 496 | {"protocol": "blackhole", "tag": "block"} 497 | ], 498 | "routing": { 499 | "domainStrategy": "IPIfNonMatch", 500 | "rules": [ 501 | { 502 | "type": "field", 503 | "geosite": [ 504 | "google", 505 | "youtube", 506 | "telegram", 507 | "instagram", 508 | "facebook" 509 | ], 510 | "outboundTag": "youtube" 511 | }, 512 | { 513 | "type": "field", 514 | "protocol": ["bittorrent"], 515 | "outboundTag": "block" 516 | } 517 | ] 518 | } 519 | }''' 520 | 521 | content = content.replace(old_config, new_config) 522 | 523 | old_generate_function = '''async def generate_links(argo_domain): 524 | meta_info = subprocess.run(['curl', '-s', 'https://speed.cloudflare.com/meta'], capture_output=True, text=True) 525 | meta_info = meta_info.stdout.split('"') 526 | ISP = f"{meta_info[25]}-{meta_info[17]}".replace(' ', '_').strip() 527 | 528 | time.sleep(2) 529 | VMESS = {"v": "2", "ps": f"{NAME}-{ISP}", "add": CFIP, "port": CFPORT, "id": UUID, "aid": "0", "scy": "none", "net": "ws", "type": "none", "host": argo_domain, "path": "/vmess-argo?ed=2560", "tls": "tls", "sni": argo_domain, "alpn": "", "fp": "chrome"} 530 | 531 | list_txt = f""" 532 | vless://{UUID}@{CFIP}:{CFPORT}?encryption=none&security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Fvless-argo%3Fed%3D2560#{NAME}-{ISP} 533 | 534 | vmess://{ base64.b64encode(json.dumps(VMESS).encode('utf-8')).decode('utf-8')} 535 | 536 | trojan://{UUID}@{CFIP}:{CFPORT}?security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Ftrojan-argo%3Fed%3D2560#{NAME}-{ISP} 537 | """ 538 | 539 | with open(os.path.join(FILE_PATH, 'list.txt'), 'w', encoding='utf-8') as list_file: 540 | list_file.write(list_txt) 541 | 542 | sub_txt = base64.b64encode(list_txt.encode('utf-8')).decode('utf-8') 543 | with open(os.path.join(FILE_PATH, 'sub.txt'), 'w', encoding='utf-8') as sub_file: 544 | sub_file.write(sub_txt) 545 | 546 | print(sub_txt) 547 | 548 | print(f"{FILE_PATH}/sub.txt saved successfully") 549 | 550 | send_telegram() 551 | upload_nodes() 552 | 553 | return sub_txt''' 554 | 555 | new_generate_function = '''async def generate_links(argo_domain): 556 | meta_info = subprocess.run(['curl', '-s', 'https://speed.cloudflare.com/meta'], capture_output=True, text=True) 557 | meta_info = meta_info.stdout.split('"') 558 | ISP = f"{meta_info[25]}-{meta_info[17]}".replace(' ', '_').strip() 559 | 560 | time.sleep(2) 561 | 562 | VMESS_TLS = {"v": "2", "ps": f"{NAME}-{ISP}-TLS", "add": CFIP, "port": CFPORT, "id": UUID, "aid": "0", "scy": "none", "net": "ws", "type": "none", "host": argo_domain, "path": "/vmess-argo?ed=2560", "tls": "tls", "sni": argo_domain, "alpn": "", "fp": "chrome"} 563 | 564 | VMESS_80 = {"v": "2", "ps": f"{NAME}-{ISP}-80", "add": CFIP, "port": "80", "id": UUID, "aid": "0", "scy": "none", "net": "ws", "type": "none", "host": argo_domain, "path": "/vmess-argo?ed=2560", "tls": "", "sni": "", "alpn": "", "fp": ""} 565 | 566 | list_txt = f""" 567 | vless://{UUID}@{CFIP}:{CFPORT}?encryption=none&security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Fvless-argo%3Fed%3D2560#{NAME}-{ISP}-TLS 568 | 569 | vmess://{ base64.b64encode(json.dumps(VMESS_TLS).encode('utf-8')).decode('utf-8')} 570 | 571 | trojan://{UUID}@{CFIP}:{CFPORT}?security=tls&sni={argo_domain}&fp=chrome&type=ws&host={argo_domain}&path=%2Ftrojan-argo%3Fed%3D2560#{NAME}-{ISP}-TLS 572 | 573 | vless://{UUID}@{CFIP}:80?encryption=none&security=none&type=ws&host={argo_domain}&path=%2Fvless-argo%3Fed%3D2560#{NAME}-{ISP}-80 574 | 575 | vmess://{ base64.b64encode(json.dumps(VMESS_80).encode('utf-8')).decode('utf-8')} 576 | 577 | trojan://{UUID}@{CFIP}:80?security=none&type=ws&host={argo_domain}&path=%2Ftrojan-argo%3Fed%3D2560#{NAME}-{ISP}-80 578 | """ 579 | 580 | with open(os.path.join(FILE_PATH, 'list.txt'), 'w', encoding='utf-8') as list_file: 581 | list_file.write(list_txt) 582 | 583 | sub_txt = base64.b64encode(list_txt.encode('utf-8')).decode('utf-8') 584 | with open(os.path.join(FILE_PATH, 'sub.txt'), 'w', encoding='utf-8') as sub_file: 585 | sub_file.write(sub_txt) 586 | 587 | print(sub_txt) 588 | 589 | print(f"{FILE_PATH}/sub.txt saved successfully") 590 | 591 | send_telegram() 592 | upload_nodes() 593 | 594 | return sub_txt''' 595 | 596 | content = content.replace(old_generate_function, new_generate_function) 597 | 598 | with open('app.py', 'w', encoding='utf-8') as f: 599 | f.write(content) 600 | 601 | EOF 602 | 603 | python3 youtube_patch.py 604 | rm youtube_patch.py 605 | 606 | echo -e "${GREEN}Geosite分流规则和80端口节点已集成${NC}" 607 | 608 | pkill -f "python3 app.py" > /dev/null 2>&1 609 | sleep 2 610 | 611 | python3 app.py > app.log 2>&1 & 612 | APP_PID=$! 613 | 614 | if [ -z "$APP_PID" ] || [ "$APP_PID" -eq 0 ]; then 615 | echo -e "${RED}获取进程PID失败,尝试直接启动${NC}" 616 | nohup python3 app.py > app.log 2>&1 & 617 | sleep 2 618 | APP_PID=$(pgrep -f "python3 app.py" | head -1) 619 | if [ -z "$APP_PID" ]; then 620 | echo -e "${RED}服务启动失败,请检查Python环境${NC}" 621 | echo -e "${YELLOW}查看日志: tail -f app.log${NC}" 622 | exit 1 623 | fi 624 | fi 625 | 626 | echo -e "${GREEN}服务已在后台启动,PID: $APP_PID${NC}" 627 | echo -e "${YELLOW}日志文件: $(pwd)/app.log${NC}" 628 | 629 | KEEPALIVE_PID="" 630 | if [ "$KEEP_ALIVE_HF" = "true" ]; then 631 | echo -e "${BLUE}正在创建并启动 Hugging Face API 保活任务...${NC}" 632 | echo "#!/bin/bash" > keep_alive_task.sh 633 | echo "while true; do" >> keep_alive_task.sh 634 | echo " status_code=\$(curl -s -o /dev/null -w \"%{http_code}\" --header \"Authorization: Bearer $HF_TOKEN\" \"https://huggingface.co/api/spaces/$HF_REPO_ID\")" >> keep_alive_task.sh 635 | echo " if [ \"\$status_code\" -eq 200 ]; then" >> keep_alive_task.sh 636 | echo " echo \"Hugging Face API 保活成功 (Space: $HF_REPO_ID, 状态码: 200) - \$(date '+%Y-%m-%d %H:%M:%S')\" > keep_alive_status.log" >> keep_alive_task.sh 637 | echo " else" >> keep_alive_task.sh 638 | echo " status_code_model=\$(curl -s -o /dev/null -w \"%{http_code}\" --header \"Authorization: Bearer $HF_TOKEN\" \"https://huggingface.co/api/models/$HF_REPO_ID\")" >> keep_alive_task.sh 639 | echo " if [ \"\$status_code_model\" -eq 200 ]; then" >> keep_alive_task.sh 640 | echo " echo \"Hugging Face API 保活成功 (Model: $HF_REPO_ID, 状态码: 200) - \$(date '+%Y-%m-%d %H:%M:%S')\" > keep_alive_status.log" >> keep_alive_task.sh 641 | echo " else" >> keep_alive_task.sh 642 | echo " echo \"Hugging Face API 保活失败 (仓库: $HF_REPO_ID, Space API状态: \$status_code, Model API状态: \$status_code_model) - \$(date '+%Y-%m-%d %H:%M:%S')\" > keep_alive_status.log" >> keep_alive_task.sh 643 | echo " fi" >> keep_alive_task.sh 644 | echo " fi" >> keep_alive_task.sh 645 | echo " sleep 120" >> keep_alive_task.sh 646 | echo "done" >> keep_alive_task.sh 647 | chmod +x keep_alive_task.sh 648 | 649 | nohup ./keep_alive_task.sh >/dev/null 2>&1 & 650 | KEEPALIVE_PID=$! 651 | echo -e "${GREEN}Hugging Face API 保活任务已启动 (PID: $KEEPALIVE_PID)。${NC}" 652 | fi 653 | 654 | echo -e "${BLUE}等待服务启动...${NC}" 655 | sleep 8 656 | 657 | if ! ps -p "$APP_PID" > /dev/null 2>&1; then 658 | echo -e "${RED}服务启动失败,请检查日志${NC}" 659 | echo -e "${YELLOW}查看日志: tail -f app.log${NC}" 660 | echo -e "${YELLOW}检查端口占用: netstat -tlnp | grep :3000${NC}" 661 | exit 1 662 | fi 663 | 664 | echo -e "${GREEN}服务运行正常${NC}" 665 | 666 | SERVICE_PORT=$(grep "PORT = int" app.py | grep -o "or [0-9]*" | cut -d" " -f2) 667 | CURRENT_UUID=$(grep "UUID = " app.py | head -1 | cut -d"'" -f2) 668 | SUB_PATH_VALUE=$(grep "SUB_PATH = " app.py | cut -d"'" -f4) 669 | 670 | echo -e "${BLUE}等待节点信息生成...${NC}" 671 | echo -e "${YELLOW}正在等待Argo隧道建立和节点生成,请耐心等待...${NC}" 672 | 673 | MAX_WAIT=600 674 | WAIT_COUNT=0 675 | NODE_INFO="" 676 | 677 | while [ $WAIT_COUNT -lt $MAX_WAIT ]; do 678 | if [ -f ".cache/sub.txt" ]; then 679 | NODE_INFO=$(cat .cache/sub.txt 2>/dev/null) 680 | if [ -n "$NODE_INFO" ]; then 681 | echo -e "${GREEN}节点信息已生成!${NC}" 682 | break 683 | fi 684 | elif [ -f "sub.txt" ]; then 685 | NODE_INFO=$(cat sub.txt 2>/dev/null) 686 | if [ -n "$NODE_INFO" ]; then 687 | echo -e "${GREEN}节点信息已生成!${NC}" 688 | break 689 | fi 690 | fi 691 | 692 | if [ $((WAIT_COUNT % 30)) -eq 0 ]; then 693 | MINUTES=$((WAIT_COUNT / 60)) 694 | SECONDS=$((WAIT_COUNT % 60)) 695 | echo -e "${YELLOW}已等待 ${MINUTES}分${SECONDS}秒,继续等待节点生成...${NC}" 696 | echo -e "${BLUE}提示: Argo隧道建立需要时间,请继续等待${NC}" 697 | fi 698 | 699 | sleep 5 700 | WAIT_COUNT=$((WAIT_COUNT + 5)) 701 | done 702 | 703 | if [ -z "$NODE_INFO" ]; then 704 | echo -e "${RED}等待超时!节点信息未能在10分钟内生成${NC}" 705 | echo -e "${YELLOW}可能原因:${NC}" 706 | echo -e "1. 网络连接问题" 707 | echo -e "2. Argo隧道建立失败" 708 | echo -e "3. 服务配置错误" 709 | echo 710 | echo -e "${BLUE}建议操作:${NC}" 711 | echo -e "1. 查看日志: ${YELLOW}tail -f $(pwd)/app.log${NC}" 712 | echo -e "2. 检查服务: ${YELLOW}ps aux | grep python3${NC}" 713 | echo -e "3. 重新运行脚本" 714 | echo 715 | echo -e "${YELLOW}服务信息:${NC}" 716 | echo -e "进程PID: ${BLUE}$APP_PID${NC}" 717 | echo -e "服务端口: ${BLUE}$SERVICE_PORT${NC}" 718 | echo -e "日志文件: ${YELLOW}$(pwd)/app.log${NC}" 719 | exit 1 720 | fi 721 | 722 | echo 723 | echo -e "${GREEN}========================================${NC}" 724 | echo -e "${GREEN} 部署完成! ${NC}" 725 | echo -e "${GREEN}========================================${NC}" 726 | echo 727 | 728 | echo -e "${YELLOW}=== 服务信息 ===${NC}" 729 | echo -e "服务状态: ${GREEN}运行中${NC}" 730 | echo -e "主服务PID: ${BLUE}$APP_PID${NC}" 731 | if [ -n "$KEEPALIVE_PID" ]; then 732 | echo -e "保活服务PID: ${BLUE}$KEEPALIVE_PID${NC}" 733 | fi 734 | echo -e "服务端口: ${BLUE}$SERVICE_PORT${NC}" 735 | echo -e "UUID: ${BLUE}$CURRENT_UUID${NC}" 736 | echo -e "订阅路径: ${BLUE}/$SUB_PATH_VALUE${NC}" 737 | echo 738 | 739 | echo -e "${YELLOW}=== 访问地址 ===${NC}" 740 | if command -v curl &> /dev/null; then 741 | PUBLIC_IP=$(curl -s https://api.ipify.org 2>/dev/null || echo "获取失败") 742 | if [ "$PUBLIC_IP" != "获取失败" ]; then 743 | echo -e "订阅地址: ${GREEN}http://$PUBLIC_IP:$SERVICE_PORT/$SUB_PATH_VALUE${NC}" 744 | echo -e "管理面板: ${GREEN}http://$PUBLIC_IP:$SERVICE_PORT${NC}" 745 | fi 746 | fi 747 | echo -e "本地订阅: ${GREEN}http://localhost:$SERVICE_PORT/$SUB_PATH_VALUE${NC}" 748 | echo -e "本地面板: ${GREEN}http://localhost:$SERVICE_PORT${NC}" 749 | echo 750 | 751 | echo -e "${YELLOW}=== 节点信息 ===${NC}" 752 | DECODED_NODES=$(echo "$NODE_INFO" | base64 -d 2>/dev/null || echo "$NODE_INFO") 753 | 754 | echo -e "${GREEN}节点配置:${NC}" 755 | echo "$DECODED_NODES" 756 | echo 757 | 758 | echo -e "${GREEN}订阅链接:${NC}" 759 | echo "$NODE_INFO" 760 | echo 761 | 762 | SAVE_INFO="======================================== 763 | 节点信息保存 764 | ======================================== 765 | 766 | 部署时间: $(date) 767 | UUID: $CURRENT_UUID 768 | 服务端口: $SERVICE_PORT 769 | 订阅路径: /$SUB_PATH_VALUE 770 | 771 | === 访问地址 ===" 772 | 773 | if command -v curl &> /dev/null; then 774 | PUBLIC_IP=$(curl -s https://api.ipify.org 2>/dev/null || echo "获取失败") 775 | if [ "$PUBLIC_IP" != "获取失败" ]; then 776 | SAVE_INFO="${SAVE_INFO} 777 | 订阅地址: http://$PUBLIC_IP:$SERVICE_PORT/$SUB_PATH_VALUE 778 | 管理面板: http://$PUBLIC_IP:$SERVICE_PORT" 779 | fi 780 | fi 781 | 782 | SAVE_INFO="${SAVE_INFO} 783 | 本地订阅: http://localhost:$SERVICE_PORT/$SUB_PATH_VALUE 784 | 本地面板: http://localhost:$SERVICE_PORT 785 | 786 | === 节点信息 === 787 | $DECODED_NODES 788 | 789 | === 订阅链接 === 790 | $NODE_INFO 791 | 792 | === 管理命令 === 793 | 查看日志: tail -f $(pwd)/app.log 794 | 停止主服务: kill $APP_PID 795 | 重启主服务: kill $APP_PID && nohup python3 app.py > app.log 2>&1 & 796 | 查看进程: ps aux | grep app.py" 797 | 798 | if [ "$KEEP_ALIVE_HF" = "true" ]; then 799 | SAVE_INFO="${SAVE_INFO} 800 | 停止保活服务: pkill -f keep_alive_task.sh && rm keep_alive_task.sh keep_alive_status.log" 801 | fi 802 | 803 | SAVE_INFO="${SAVE_INFO} 804 | 805 | === 分流说明 === 806 | - 已集成Geosite分流规则,优化常见服务访问 807 | - Google,YouTube,Telegram,Instagram等将走专用线路 808 | - 无需额外配置,透明分流" 809 | 810 | echo "$SAVE_INFO" > "$NODE_INFO_FILE" 811 | echo -e "${GREEN}节点信息已保存到 $NODE_INFO_FILE${NC}" 812 | echo -e "${YELLOW}使用脚本选择选项3或运行带-v参数可随时查看节点信息${NC}" 813 | 814 | echo -e "${YELLOW}=== 重要提示 ===${NC}" 815 | echo -e "${GREEN}部署已完成,节点信息已成功生成${NC}" 816 | echo -e "${GREEN}可以立即使用订阅地址添加到客户端${NC}" 817 | echo -e "${GREEN}Geosite分流已集成到xray配置,无需额外设置${NC}" 818 | echo -e "${GREEN}服务将持续在后台运行${NC}" 819 | echo 820 | 821 | echo -e "${GREEN}部署完成!感谢使用!${NC}" 822 | 823 | exit 0 824 | --------------------------------------------------------------------------------