更新 host_ollama.py

This commit is contained in:
kanshan 2025-03-27 16:46:32 +08:00
parent c7464e0e4d
commit 70b1d02cba

View File

@ -1,113 +1,150 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Ollama 本地镜像服务器 Ollama 本地镜像服务器 - 增强版
功能 新增功能
1. 自动检测并Host下载的镜像目录 1. 同时托管 ollama_releases install.sh
2. 提供文件浏览和下载接口 2. 自动生成安装指引
3. 自动生成友好的下载页面 3. 显示本机IP方便局域网访问
""" """
import os import os
import sys import sys
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path from pathlib import Path
import threading import threading
import webbrowser import webbrowser
MIRROR_DIR = "ollama_releases" # 与mirror_ollama.sh保持一致 MIRROR_DIR = "ollama_releases"
INSTALL_SCRIPT = "install.sh"
PORT = 8000 PORT = 8000
class OllamaRequestHandler(SimpleHTTPRequestHandler): class OllamaRequestHandler(SimpleHTTPRequestHandler):
"""自定义请求处理器,增强目录浏览功能""" """自定义请求处理器,支持多目录托管"""
def __init__(self, *args, **kwargs): def translate_path(self, path):
super().__init__(*args, directory=MIRROR_DIR, **kwargs) # 优先检查是否是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): def list_directory(self, path):
"""生成增强型目录列表页面""" """生成增强型目录列表页面"""
try: 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 mtime = item.stat().st_mtime
size = item.stat().st_size size = item.stat().st_size
items.append((item.name, mtime, size)) items.append((item.name, mtime, size))
# 按版本号排序(降序) items.sort(key=lambda x: x[0], reverse=True)
items.sort(key=lambda x: x[0], reverse=True)
html = [
# 生成HTML "<html><head><title>Ollama 本地镜像</title>",
html = ["<html><head><title>Ollama 本地镜像</title>"] "<style>",
html.append("<style>") "body { font-family: Arial, sans-serif; margin: 20px; }",
html.append("body { font-family: Arial, sans-serif; margin: 20px; }") "table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }",
html.append("table { border-collapse: collapse; width: 100%; }") "th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }",
html.append("th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }") "tr:hover { background-color: #f5f5f5; }",
html.append("tr:hover { background-color: #f5f5f5; }") ".box { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }",
html.append("</style></head><body>") "</style></head><body>",
html.append("<h1>Ollama 本地镜像库</h1>") f"<h1>Ollama 本地镜像库</h1>",
html.append(f"<p>当前目录: {path}</p>") f"<div class='box'>",
html.append("<table><tr><th>文件名</th><th>大小</th><th>修改时间</th></tr>") "<h3>📌 安装指引</h3>",
f"<p>在其他机器上执行以下命令安装:</p>",
for name, mtime, size in items: f"<code>curl -sSL {install_url} | sudo bash</code>",
human_size = f"{size/1024/1024:.1f} MB" if size > 1024*1024 else f"{size/1024:.1f} KB" f"</div>",
html.append( "<h3>📂 可用文件列表</h3>",
f'<tr><td><a href="{name}">{name}</a></td>' "<table><tr><th>文件名</th><th>大小</th><th>修改时间</th></tr>"
f'<td>{human_size}</td>' ]
f'<td>{mtime:.0f}</td></tr>'
) 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("</table></body></html>") html.append(
self.send_response(200) f'<tr><td><a href="{name}">{name}</a></td>'
self.send_header("Content-type", "text/html; charset=utf-8") f'<td>{human_size}</td>'
self.end_headers() f'<td>{mtime:.0f}</td></tr>'
self.wfile.write("\n".join(html).encode('utf-8')) )
except Exception as e: html.append("</table></body></html>")
self.send_error(500, f"服务器错误: {str(e)}")
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): def start_server(port=PORT):
"""启动HTTP服务器""" """启动HTTP服务器"""
local_ip = get_local_ip()
server_address = ('', port) server_address = ('', port)
httpd = HTTPServer(server_address, OllamaRequestHandler) httpd = HTTPServer(server_address, OllamaRequestHandler)
print(f"\n🎯 Ollama 本地镜像服务已启动") 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(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: try:
httpd.serve_forever() httpd.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n服务器已停止") print("\n服务器已停止")
def ensure_mirror_dir(): def check_files():
"""确保镜像目录存在""" """检查必要文件是否存在"""
if not os.path.exists(MIRROR_DIR): if not os.path.exists(MIRROR_DIR):
os.makedirs(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 False
return True return True
def open_browser():
"""自动打开浏览器"""
import time
time.sleep(1)
webbrowser.open(f"http://localhost:{PORT}")
if __name__ == '__main__': if __name__ == '__main__':
# 检查并获取端口参数 port = int(sys.argv[1]) if len(sys.argv) > 1 else PORT
port = PORT
if len(sys.argv) > 1:
try:
port = int(sys.argv[1])
except ValueError:
print("错误: 端口号必须是数字")
sys.exit(1)
if not ensure_mirror_dir(): if not check_files():
print("⚠️ 注意: 镜像目录为空,请先运行 mirror_ollama.sh 下载文件") 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) start_server(port)