Spaces:
Paused
Paused
| """ | |
| Kaggle/Colab 启动脚本 | |
| 用于启动 FastAPI 服务器并配置 ngrok 穿透 | |
| """ | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| import threading | |
| import re | |
| import shutil | |
| def install_ngrok(max_retries: int = 3): | |
| """安装 pyngrok 和 cloudflared(使用国内镜像,失败自动重试)""" | |
| print("🔧 正在安装 Web 穿透工具...") | |
| mirrors = [ | |
| "https://pypi.tuna.tsinghua.edu.cn/simple", | |
| "https://mirrors.aliyun.com/pypi/simple", | |
| None, # 退回默认源 | |
| ] | |
| for attempt in range(1, max_retries + 1): | |
| mirror = mirrors[min(attempt - 1, len(mirrors) - 1)] | |
| cmd = [ | |
| sys.executable, | |
| "-m", | |
| "pip", | |
| "install", | |
| "--no-cache-dir", | |
| "--default-timeout", | |
| "120", | |
| ] | |
| if mirror: | |
| cmd.extend(["-i", mirror]) | |
| cmd.extend(["pyngrok", "cloudflared"]) | |
| try: | |
| print(f"⏳ 第 {attempt} 次安装,使用源: {mirror or '默认 PyPI'}") | |
| subprocess.check_call(cmd) | |
| print("✅ 穿透工具安装完成") | |
| return True | |
| except subprocess.CalledProcessError as e: | |
| print(f"⚠️ 安装失败: {e}") | |
| time.sleep(5) | |
| print("❌ 多次尝试后仍无法安装 pyngrok/cloudflared") | |
| return False | |
| def run_server(): | |
| """在后台运行服务器""" | |
| print("🚀 启动 FastAPI 服务器...") | |
| subprocess.Popen([sys.executable, "server.py"]) | |
| def start_ngrok(): | |
| try: | |
| from pyngrok import ngrok | |
| token = os.environ.get("NGROK_AUTHTOKEN") | |
| if not token: | |
| print("\n⚠️ 警告: 未设置 NGROK_AUTHTOKEN 环境变量") | |
| return False | |
| ngrok.set_auth_token(token) | |
| public_url = ngrok.connect(8000).public_url | |
| print("\n" + "="*60) | |
| print("✅ 成功穿透! 公网访问地址:") | |
| print(f"👉 {public_url}") | |
| print("="*60 + "\n") | |
| try: | |
| while True: | |
| time.sleep(1) | |
| except KeyboardInterrupt: | |
| ngrok.kill() | |
| return True | |
| except ImportError: | |
| print("❌ pyngrok 导入失败,请确保已安装") | |
| return False | |
| except Exception as e: | |
| print(f"❌ ngrok 启动失败: {e}") | |
| return False | |
| def start_cloudflared(): | |
| try: | |
| cmd = None | |
| # 1. 检查系统路径 | |
| if shutil.which("cloudflared"): | |
| cmd = ["cloudflared", "tunnel", "--url", "http://localhost:8000", "--no-autoupdate"] | |
| # 2. 检查当前目录 | |
| elif os.path.exists("./cloudflared"): | |
| cmd = ["./cloudflared", "tunnel", "--url", "http://localhost:8000", "--no-autoupdate"] | |
| # 确保有执行权限 | |
| try: | |
| os.chmod("./cloudflared", 0o755) | |
| except: | |
| pass | |
| else: | |
| # 如果找不到 cloudflared 二进制,尝试通过 pip 安装的 cloudflared 运行 | |
| # 注意:cloudflared 的 pip 包可能不直接暴露 cloudflared 命令 | |
| # 我们尝试直接下载二进制文件 | |
| print("⚠️ 未找到 cloudflared 命令,尝试下载二进制文件...") | |
| try: | |
| # 这里简化处理,如果 pip 安装的模块无法直接运行,提示用户手动安装 | |
| # 或者尝试使用 pyngrok 作为回退 | |
| print("⚠️ 无法通过 Python 模块启动 cloudflared,将尝试仅使用 pyngrok") | |
| return | |
| except Exception: | |
| print("⚠️ 未找到 cloudflared,可通过 'pip install cloudflared' 安装,或跳过穿透") | |
| return | |
| if cmd: | |
| proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) | |
| url = None | |
| while True: | |
| line = proc.stdout.readline() | |
| if not line: | |
| break | |
| if "trycloudflare.com" in line: | |
| m = re.search(r"https?://[\w\.-]+trycloudflare\.com[\S]*", line) | |
| if m: | |
| url = m.group(0) | |
| print("\n" + "="*60) | |
| print("✅ 成功穿透! 公网访问地址:") | |
| print(f"👉 {url}") | |
| print("="*60 + "\n") | |
| break | |
| try: | |
| while True: | |
| time.sleep(1) | |
| except KeyboardInterrupt: | |
| proc.terminate() | |
| except Exception as e: | |
| print(f"❌ Cloudflare Tunnel 启动失败: {e}") | |
| if __name__ == "__main__": | |
| # 1. 安装依赖 | |
| try: | |
| import uvicorn | |
| import fastapi | |
| except ImportError: | |
| print("🔧 安装 FastAPI 依赖...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", "fastapi", "uvicorn", "python-multipart"]) | |
| try: | |
| import pyngrok | |
| except ImportError: | |
| install_ngrok() | |
| # 检查 cloudflared 是否存在,如果不存在尝试安装 | |
| if not shutil.which("cloudflared"): | |
| # 尝试作为 Python 模块调用,但先不导入它来检查,而是直接看 pip list 或依赖 subprocess | |
| # 由于 cloudflared 库可能有导入问题,我们这里只做安装尝试,不做导入检查 | |
| pass | |
| # 2. 启动 FastAPI | |
| server_thread = threading.Thread(target=run_server) | |
| server_thread.daemon = True | |
| server_thread.start() | |
| # 等待服务器启动 (循环检查端口) | |
| print("⏳ 等待服务器启动...") | |
| import socket | |
| def wait_for_port(port, host='127.0.0.1', timeout=60): | |
| start_time = time.time() | |
| while True: | |
| try: | |
| with socket.create_connection((host, port), timeout=1): | |
| print(f"✅ 服务器已在 {host}:{port} 就绪") | |
| return True | |
| except (OSError, ConnectionRefusedError): | |
| if time.time() - start_time > timeout: | |
| print(f"❌ 服务器启动超时 ({timeout}s)") | |
| return False | |
| time.sleep(1) | |
| if not wait_for_port(8000): | |
| print("❌ 服务器未能成功启动,请检查日志") | |
| sys.exit(1) | |
| use_tunnel = os.environ.get("USE_TUNNEL", "true").lower() == "true" | |
| if use_tunnel: | |
| ok = start_ngrok() | |
| if not ok: | |
| start_cloudflared() | |
| else: | |
| print("\n" + "="*60) | |
| print("✅ 服务器已启动,局域网访问地址:") | |
| print("👉 http://127.0.0.1:8000") | |
| print("="*60 + "\n") | |
| try: | |
| while True: | |
| time.sleep(1) | |
| except KeyboardInterrupt: | |
| pass | |