From 70b1d02cbad261e18fd3184eb47113e6eab70607 Mon Sep 17 00:00:00 2001 From: kanshan Date: Thu, 27 Mar 2025 16:46:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20host=5Follama.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- host_ollama.py | 169 ++++++++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 66 deletions(-) diff --git a/host_ollama.py b/host_ollama.py index bdabb0f..68a1619 100644 --- a/host_ollama.py +++ b/host_ollama.py @@ -1,113 +1,150 @@ #!/usr/bin/env python3 """ -Ollama 本地镜像服务器 -功能: -1. 自动检测并Host下载的镜像目录 -2. 提供文件浏览和下载接口 -3. 自动生成友好的下载页面 +Ollama 本地镜像服务器 - 增强版 +新增功能: +1. 同时托管 ollama_releases 和 install.sh +2. 自动生成安装指引 +3. 显示本机IP方便局域网访问 """ import os import sys +import socket from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path import threading import webbrowser -MIRROR_DIR = "ollama_releases" # 与mirror_ollama.sh保持一致 +MIRROR_DIR = "ollama_releases" +INSTALL_SCRIPT = "install.sh" PORT = 8000 class OllamaRequestHandler(SimpleHTTPRequestHandler): - """自定义请求处理器,增强目录浏览功能""" + """自定义请求处理器,支持多目录托管""" - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=MIRROR_DIR, **kwargs) + def translate_path(self, path): + # 优先检查是否是install.sh + if path.endswith('/' + INSTALL_SCRIPT) or path == '/' + INSTALL_SCRIPT: + return os.path.abspath(INSTALL_SCRIPT) + + # 默认处理镜像目录 + path = path.split('?',1)[0] + path = path.split('#',1)[0] + return os.path.abspath(os.path.join(MIRROR_DIR, path.lstrip('/'))) def list_directory(self, path): """生成增强型目录列表页面""" try: - items = [] - for item in Path(path).iterdir(): + # 特殊处理根目录 + if os.path.abspath(path) == os.path.abspath(MIRROR_DIR): + return self.generate_index_page(path) + return super().list_directory(path) + except Exception as e: + self.send_error(500, f"服务器错误: {str(e)}") + + def generate_index_page(self, path): + """生成带安装指引的首页""" + local_ip = get_local_ip() + install_url = f"http://{local_ip}:{self.server.server_port}/{INSTALL_SCRIPT}" + + items = [] + for item in Path(path).iterdir(): + if item.is_file(): mtime = item.stat().st_mtime size = item.stat().st_size items.append((item.name, mtime, size)) - - # 按版本号排序(降序) - items.sort(key=lambda x: x[0], reverse=True) - - # 生成HTML - html = ["Ollama 本地镜像"] - html.append("") - html.append("

Ollama 本地镜像库

") - html.append(f"

当前目录: {path}

") - html.append("") - - for name, mtime, size in items: - human_size = f"{size/1024/1024:.1f} MB" if size > 1024*1024 else f"{size/1024:.1f} KB" - html.append( - f'' - f'' - f'' - ) - - html.append("
文件名大小修改时间
{name}{human_size}{mtime:.0f}
") - self.send_response(200) - self.send_header("Content-type", "text/html; charset=utf-8") - self.end_headers() - self.wfile.write("\n".join(html).encode('utf-8')) - - except Exception as e: - self.send_error(500, f"服务器错误: {str(e)}") + + items.sort(key=lambda x: x[0], reverse=True) + + html = [ + "Ollama 本地镜像", + "", + f"

Ollama 本地镜像库

", + f"
", + "

📌 安装指引

", + f"

在其他机器上执行以下命令安装:

", + f"curl -sSL {install_url} | sudo bash", + f"
", + "

📂 可用文件列表

", + "" + ] + + for name, mtime, size in items: + human_size = f"{size/1024/1024:.1f} MB" if size > 1024*1024 else f"{size/1024:.1f} KB" + html.append( + f'' + f'' + f'' + ) + + html.append("
文件名大小修改时间
{name}{human_size}{mtime:.0f}
") + + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write("\n".join(html).encode('utf-8')) + +def get_local_ip(): + """获取本机局域网IP""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except: + return "localhost" def start_server(port=PORT): """启动HTTP服务器""" + local_ip = get_local_ip() server_address = ('', port) httpd = HTTPServer(server_address, OllamaRequestHandler) print(f"\n🎯 Ollama 本地镜像服务已启动") - print(f"👉 访问地址: http://localhost:{port}") + print(f"👉 本机访问: http://localhost:{port}") + print(f"👉 局域网访问: http://{local_ip}:{port}") print(f"📁 镜像目录: {os.path.abspath(MIRROR_DIR)}") - print("🛑 按 Ctrl+C 停止服务\n") + print(f"📜 安装脚本: {os.path.abspath(INSTALL_SCRIPT)}") + print("\n🔧 安装命令:") + print(f" curl -sSL http://{local_ip}:{port}/{INSTALL_SCRIPT} | sudo bash") + print("\n🛑 按 Ctrl+C 停止服务\n") try: httpd.serve_forever() except KeyboardInterrupt: print("\n服务器已停止") -def ensure_mirror_dir(): - """确保镜像目录存在""" +def check_files(): + """检查必要文件是否存在""" if not os.path.exists(MIRROR_DIR): os.makedirs(MIRROR_DIR) - print(f"创建镜像目录: {MIRROR_DIR}") + print(f"⚠️ 创建空镜像目录: {MIRROR_DIR}") + print("请先运行 mirror_ollama.sh 下载文件") + + if not os.path.exists(INSTALL_SCRIPT): + print(f"❌ 错误: {INSTALL_SCRIPT} 不存在") + print("请确保安装脚本与host_ollama.py在同一目录") return False return True -def open_browser(): - """自动打开浏览器""" - import time - time.sleep(1) - webbrowser.open(f"http://localhost:{PORT}") - if __name__ == '__main__': - # 检查并获取端口参数 - port = PORT - if len(sys.argv) > 1: - try: - port = int(sys.argv[1]) - except ValueError: - print("错误: 端口号必须是数字") - sys.exit(1) + port = int(sys.argv[1]) if len(sys.argv) > 1 else PORT - if not ensure_mirror_dir(): - print("⚠️ 注意: 镜像目录为空,请先运行 mirror_ollama.sh 下载文件") + if not check_files(): + sys.exit(1) # 自动打开浏览器 - threading.Thread(target=open_browser, daemon=True).start() + threading.Thread( + target=webbrowser.open, + args=(f"http://localhost:{port}",), + daemon=True + ).start() - # 启动服务器 start_server(port) \ No newline at end of file