150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
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"
|
|
INSTALL_SCRIPT = "install.sh"
|
|
PORT = 8000
|
|
|
|
class OllamaRequestHandler(SimpleHTTPRequestHandler):
|
|
"""自定义请求处理器,支持多目录托管"""
|
|
|
|
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:
|
|
# 特殊处理根目录
|
|
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><head><title>Ollama 本地镜像</title>",
|
|
"<style>",
|
|
"body { font-family: Arial, sans-serif; margin: 20px; }",
|
|
"table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }",
|
|
"th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }",
|
|
"tr:hover { background-color: #f5f5f5; }",
|
|
".box { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }",
|
|
"</style></head><body>",
|
|
f"<h1>Ollama 本地镜像库</h1>",
|
|
f"<div class='box'>",
|
|
"<h3>📌 安装指引</h3>",
|
|
f"<p>在其他机器上执行以下命令安装:</p>",
|
|
f"<code>curl -sSL {install_url} | sudo bash</code>",
|
|
f"</div>",
|
|
"<h3>📂 可用文件列表</h3>",
|
|
"<table><tr><th>文件名</th><th>大小</th><th>修改时间</th></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(
|
|
f'<tr><td><a href="{name}">{name}</a></td>'
|
|
f'<td>{human_size}</td>'
|
|
f'<td>{mtime:.0f}</td></tr>'
|
|
)
|
|
|
|
html.append("</table></body></html>")
|
|
|
|
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://{local_ip}:{port}")
|
|
print(f"📁 镜像目录: {os.path.abspath(MIRROR_DIR)}")
|
|
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 check_files():
|
|
"""检查必要文件是否存在"""
|
|
if not os.path.exists(MIRROR_DIR):
|
|
os.makedirs(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
|
|
|
|
if __name__ == '__main__':
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else PORT
|
|
|
|
if not check_files():
|
|
sys.exit(1)
|
|
|
|
# 自动打开浏览器
|
|
threading.Thread(
|
|
target=webbrowser.open,
|
|
args=(f"http://localhost:{port}",),
|
|
daemon=True
|
|
).start()
|
|
|
|
start_server(port) |