- CLI: --preset 实际生效;--directory-listing/--enable-stats/--enable-*/--ignore-hidden 支持 true/false(修复 type=bool 陷阱),未显式传入时不再覆盖 settings.json - 版本号统一 v2.3(settings.json/config.py/v1/v2/Dockerfile) - 移除硬编码 /tmp/pypi_debug.log 写文件;v2 裸 except 改记录错误返回 500 - webhook id 非法输入返回 400;下载趋势 timedelta 导入提升修复 NameError - 打包模式 auth_token 写入 exe 目录(不再写入 _MEIPASS) - README 修正: 线程池架构、pyinstaller 构建方式 - mirrors/__init__.py ' Quay.io' 笔误;chunked 响应去掉 Content-Length;docs/ui realpath 边界 - SQLite WAL+busy_timeout;下载计数原子 UPDATE - requirements 移除未使用的 cachetools/apscheduler
649 lines
26 KiB
Python
649 lines
26 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
HYC下载站 v2.3 - 完整增强版
|
||
主入口文件
|
||
支持镜像同步、下载加速源、系统监控、实时通信等
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import signal
|
||
import argparse
|
||
import time
|
||
|
||
# PyInstaller 资源路径处理
|
||
def get_resource_path(relative_path):
|
||
"""获取打包后的资源路径"""
|
||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||
# 打包后的路径
|
||
return os.path.join(sys._MEIPASS, relative_path)
|
||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
|
||
|
||
# 添加项目根目录到Python路径
|
||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, base_dir)
|
||
|
||
from core.config import ConfigManager, load_config_file, deep_merge, load_settings_with_override
|
||
from core.server import MirrorServer
|
||
from core.utils import parse_size
|
||
from core.database import init_database, load_db_config_from_env, merge_config
|
||
from core.sync_scheduler import init_database_sync
|
||
from core.optimization import (
|
||
MemoryManager, LowMemoryConfig, ArchitectureDetector, check_compatibility
|
||
)
|
||
|
||
|
||
def signal_handler(signum, _frame):
|
||
"""处理退出信号(优雅退出:触发 KeyboardInterrupt 走正常关闭流程)"""
|
||
print(f"\n收到信号 {signum},正在优雅关闭服务器...")
|
||
raise KeyboardInterrupt
|
||
|
||
|
||
def _parse_bool_arg(value):
|
||
"""解析布尔命令行参数(--flag true/false/1/0/yes/no)"""
|
||
if isinstance(value, bool):
|
||
return value
|
||
return str(value).strip().lower() in ('1', 'true', 'yes', 'on')
|
||
|
||
|
||
def parse_arguments():
|
||
"""解析命令行参数"""
|
||
parser = argparse.ArgumentParser(
|
||
description='HYC下载站 v2.3 - 镜像文件服务器 + 下载加速源',
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog='''
|
||
示例:
|
||
# 基本启动
|
||
python main.py -p 8080 -d ./downloads
|
||
|
||
# 使用配置文件
|
||
python main.py --settings settings.json --config custom.json
|
||
|
||
# 认证配置
|
||
python main.py --auth-type token --auth-token your_token_here
|
||
|
||
# 功能开关
|
||
python main.py --enable-monitor --enable-sync --enable-mirrors
|
||
python main.py --disable-ws --disable-sse # 适合低端设备
|
||
|
||
# 镜像加速源 (通过 settings.json 配置各个镜像的启用/禁用)
|
||
python main.py --enable-mirrors
|
||
|
||
# 低端设备优化
|
||
python main.py --preset ultra_low
|
||
python main.py --memory-limit 256M --workers 2
|
||
|
||
# 调试
|
||
python main.py --debug
|
||
python main.py --debug-types http auth api v2 error
|
||
|
||
# 检查系统兼容性
|
||
python main.py --check-compat
|
||
'''
|
||
)
|
||
|
||
# 服务器配置
|
||
parser.add_argument('--host', default='0.0.0.0', help='监听地址 (默认: 0.0.0.0)')
|
||
parser.add_argument('-p', '--port', type=int, default=8080, help='监听端口 (默认: 8080)')
|
||
parser.add_argument('-d', '--base-dir', default='./downloads', help='文件存储目录 (默认: ./downloads)')
|
||
parser.add_argument('--server-name', default='HYC下载站', help='服务器名称')
|
||
|
||
# HTTPS配置
|
||
parser.add_argument('--ssl-cert', help='SSL证书文件路径')
|
||
parser.add_argument('--ssl-key', help='SSL私钥文件路径')
|
||
|
||
# 认证配置
|
||
auth_group = parser.add_argument_group('认证配置')
|
||
auth_group.add_argument('--auth-type', choices=['none', 'basic', 'token'], default=None,
|
||
help='认证类型: none(无), basic(基本认证), token(令牌认证)')
|
||
auth_group.add_argument('--auth-user', default='admin', help='基本认证用户名')
|
||
auth_group.add_argument('--auth-pass', help='基本认证密码')
|
||
auth_group.add_argument('--auth-token', help='令牌认证密钥')
|
||
|
||
# 功能配置
|
||
func_group = parser.add_argument_group('功能配置')
|
||
func_group.add_argument('--directory-listing', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用目录浏览 (默认: True; 传 false 可禁用)')
|
||
func_group.add_argument('--enable-stats', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用下载统计 (默认: True; 传 false 可禁用)')
|
||
func_group.add_argument('--show-hash', action='store_true', help='显示文件哈希值')
|
||
func_group.add_argument('--ignore-hidden', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='忽略隐藏文件 (默认: True; 传 false 显示隐藏文件)')
|
||
func_group.add_argument('--max-upload-size', default='1G', help='最大上传文件大小')
|
||
func_group.add_argument('--api-version', choices=['v1', 'v2'], default='v2',
|
||
help='API版本 (默认: v2)')
|
||
|
||
# 实时通信配置
|
||
realtime_group = parser.add_argument_group('实时通信配置')
|
||
realtime_group.add_argument('--enable-ws', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用WebSocket (默认: True; 传 false 可禁用)')
|
||
realtime_group.add_argument('--enable-sse', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用SSE (默认: True; 传 false 可禁用)')
|
||
|
||
# 系统监控配置
|
||
monitor_group = parser.add_argument_group('系统监控配置')
|
||
monitor_group.add_argument('--enable-monitor', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用系统监控 (默认: True; 传 false 可禁用)')
|
||
monitor_group.add_argument('--monitor-interval', type=int, default=5,
|
||
help='监控数据采集间隔(秒) (默认: 5)')
|
||
|
||
# 同步配置
|
||
sync_group = parser.add_argument_group('同步配置')
|
||
sync_group.add_argument('--enable-sync', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用镜像同步 (默认: True; 传 false 可禁用)')
|
||
sync_group.add_argument('--sync-config', help='同步配置文件路径')
|
||
|
||
# 镜像加速源配置
|
||
mirror_group = parser.add_argument_group('镜像加速源配置')
|
||
mirror_group.add_argument('--enable-mirrors', nargs='?', const=True, default=None,
|
||
type=_parse_bool_arg,
|
||
help='启用下载加速源 (默认: True; 传 false 可禁用)')
|
||
# 注意: 各个镜像的启用/禁用通过 settings.json 中的 mirrors.xxx.enabled 配置
|
||
|
||
# 下载限速
|
||
rate_group = parser.add_argument_group('下载限速配置')
|
||
rate_group.add_argument('--rate-limit', type=int, default=0,
|
||
help='全局下载限速(字节/秒) (默认: 0=不限速)')
|
||
|
||
# 低端设备优化配置
|
||
optimize_group = parser.add_argument_group('低端设备优化配置')
|
||
optimize_group.add_argument('--preset', choices=['ultra_low', 'low', 'medium', 'high', 'auto'],
|
||
default='auto', help='设备预设 (默认: auto)')
|
||
optimize_group.add_argument('--memory-limit', default='512M',
|
||
help='内存限制 (例如: 256M, 512M, 1G)')
|
||
optimize_group.add_argument('--workers', type=int, default=0,
|
||
help='工作进程数 (0=自动)')
|
||
optimize_group.add_argument('--chunk-size', default='128K',
|
||
help='文件传输块大小 (默认: 128K)')
|
||
optimize_group.add_argument('--buffer-size', default='256K',
|
||
help='缓冲区大小 (默认: 256K)')
|
||
optimize_group.add_argument('--disable-ws', action='store_true',
|
||
help='禁用WebSocket (低端设备)')
|
||
optimize_group.add_argument('--disable-sse', action='store_true',
|
||
help='禁用SSE (低端设备)')
|
||
optimize_group.add_argument('--disable-hash', action='store_true',
|
||
help='禁用文件哈希计算 (低端设备)')
|
||
optimize_group.add_argument('--check-compat', action='store_true',
|
||
help='检查系统兼容性后退出')
|
||
|
||
# 日志配置
|
||
log_group = parser.add_argument_group('日志配置')
|
||
log_group.add_argument('--access-log', help='访问日志文件路径')
|
||
log_group.add_argument('--verbose', '-v', action='count', default=0, help='详细输出')
|
||
log_group.add_argument('--quiet', '-q', action='store_true', help='静默模式')
|
||
|
||
# 细粒度调试开关
|
||
debug_group = parser.add_argument_group('调试选项')
|
||
debug_group.add_argument('--debug', '-D', action='store_true',
|
||
help='启用所有调试输出')
|
||
debug_group.add_argument('--debug-log', dest='debug_log_file',
|
||
help='调试日志文件路径 (debug 输出将写入此文件)')
|
||
debug_group.add_argument('--debug-http', action='store_true',
|
||
help='调试 HTTP 请求')
|
||
debug_group.add_argument('--debug-auth', action='store_true',
|
||
help='调试 认证检查')
|
||
debug_group.add_argument('--debug-api', action='store_true',
|
||
help='调试 API 路由')
|
||
debug_group.add_argument('--debug-v2', action='store_true',
|
||
help='调试 V2 API')
|
||
debug_group.add_argument('--debug-error', action='store_true',
|
||
help='调试 错误堆栈')
|
||
debug_group.add_argument('--debug-download', action='store_true',
|
||
help='调试 下载记录')
|
||
debug_group.add_argument('--debug-types', '--debug-list',
|
||
nargs='+', metavar='TYPE',
|
||
choices=['http', 'auth', 'api', 'v2', 'error', 'download'],
|
||
help='指定调试类型列表 (http auth api v2 error download)')
|
||
|
||
# 配置文件
|
||
parser.add_argument('--settings', '--default-config', dest='settings',
|
||
help='默认配置文件路径 (settings.json)')
|
||
parser.add_argument('--config', help='覆盖配置文件路径 (JSON格式,会覆盖默认配置)')
|
||
|
||
return parser.parse_args()
|
||
|
||
|
||
def build_config_from_args(args):
|
||
"""从命令行参数构建配置"""
|
||
cmd_config = {}
|
||
|
||
# 服务器基本配置
|
||
basic_args = ['host', 'port', 'server_name', 'ssl_cert', 'ssl_key',
|
||
'auth_type', 'auth_user', 'auth_pass', 'auth_token',
|
||
'access_log', 'verbose',
|
||
'api_version', 'directory_listing',
|
||
'enable_stats', 'show_hash', 'ignore_hidden', 'max_upload_size']
|
||
|
||
for arg_name in basic_args:
|
||
arg_value = getattr(args, arg_name, None)
|
||
if arg_value is not None:
|
||
cmd_config[arg_name] = arg_value
|
||
|
||
# Debug 配置处理
|
||
# 优先级: --debug > --debug-types > individual --debug-xxx
|
||
debug_types = []
|
||
if args.debug_types:
|
||
# 用户指定了类型列表
|
||
debug_types = list(args.debug_types)
|
||
cmd_config['debug'] = debug_types
|
||
elif args.debug_http or args.debug_auth or args.debug_api or args.debug_v2 or args.debug_error or args.debug_download:
|
||
# 用户指定了单个类型
|
||
if args.debug_http:
|
||
debug_types.append('http')
|
||
if args.debug_auth:
|
||
debug_types.append('auth')
|
||
if args.debug_api:
|
||
debug_types.append('api')
|
||
if args.debug_v2:
|
||
debug_types.append('v2')
|
||
if args.debug_error:
|
||
debug_types.append('error')
|
||
if args.debug_download:
|
||
debug_types.append('download')
|
||
cmd_config['debug'] = debug_types
|
||
elif args.debug:
|
||
# 开启所有
|
||
cmd_config['debug'] = True
|
||
else:
|
||
# 未开启任何 debug
|
||
cmd_config['debug'] = False
|
||
|
||
# Debug 日志文件
|
||
if args.debug_log_file:
|
||
cmd_config['debug_log_file'] = args.debug_log_file
|
||
|
||
# 路径处理
|
||
if args.base_dir:
|
||
cmd_config['base_dir'] = os.path.abspath(args.base_dir)
|
||
|
||
# 实时通信(仅在显式传入时覆盖 settings.json)
|
||
if args.enable_ws is not None:
|
||
cmd_config['enable_ws'] = args.enable_ws
|
||
if args.enable_sse is not None:
|
||
cmd_config['enable_sse'] = args.enable_sse
|
||
|
||
# 系统监控
|
||
if args.enable_monitor is not None:
|
||
cmd_config['enable_monitor'] = args.enable_monitor
|
||
cmd_config['monitor_interval'] = args.monitor_interval
|
||
|
||
# 同步
|
||
if args.enable_sync is not None:
|
||
cmd_config['enable_sync'] = args.enable_sync
|
||
|
||
# 镜像加速源
|
||
if args.enable_mirrors is not None:
|
||
cmd_config['enable_mirrors'] = args.enable_mirrors
|
||
|
||
# 设备预设(--preset 实际生效)
|
||
if args.preset:
|
||
cmd_config['preset'] = args.preset
|
||
# 各个镜像的启用/禁用通过 settings.json 中的 mirrors.xxx.enabled 配置
|
||
|
||
# 下载限速
|
||
cmd_config['rate_limit'] = args.rate_limit
|
||
|
||
# 静默模式
|
||
if args.quiet:
|
||
cmd_config['verbose'] = -1
|
||
|
||
return cmd_config
|
||
|
||
|
||
def main():
|
||
"""主入口函数"""
|
||
args = parse_arguments()
|
||
|
||
# 确定默认配置文件路径
|
||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||
settings_path = args.settings or os.path.join(project_root, 'settings.json')
|
||
|
||
# 打印配置来源信息
|
||
print(f"[配置加载]")
|
||
print(f" 默认配置: {settings_path}")
|
||
|
||
# 加载配置(优先级从低到高:默认配置 -> 覆盖配置 -> 环境变量 -> 命令行参数)
|
||
# 使用深度合并,只替换覆盖配置中有的字段
|
||
|
||
# 1. 加载默认配置 (settings.json)
|
||
from core.config import load_json_config
|
||
default_config = load_json_config(settings_path) or {}
|
||
print(f" 默认配置加载: {'成功' if default_config else '使用内联默认'}")
|
||
|
||
# 2. 从覆盖配置文件加载
|
||
override_config = {}
|
||
if args.config:
|
||
override_config = load_json_config(args.config) or {}
|
||
print(f" 覆盖配置: {args.config} ({len(override_config)} 个顶层键)")
|
||
else:
|
||
print(f" 覆盖配置: 未指定")
|
||
|
||
# 3. 深度合并默认配置和覆盖配置
|
||
config = deep_merge(default_config, override_config)
|
||
|
||
# 4. 从环境变量加载数据库配置
|
||
env_db_config = load_db_config_from_env()
|
||
if env_db_config:
|
||
config = deep_merge(config, env_db_config)
|
||
|
||
# 5. 同步配置
|
||
if args.sync_config:
|
||
sync_config = load_json_config(args.sync_config)
|
||
if sync_config:
|
||
if 'sync_sources' in sync_config:
|
||
config['sync_sources'] = deep_merge(
|
||
config.get('sync_sources', {}),
|
||
sync_config['sync_sources']
|
||
)
|
||
if 'mirrors' in sync_config:
|
||
config['mirrors'] = deep_merge(
|
||
config.get('mirrors', {}),
|
||
sync_config['mirrors']
|
||
)
|
||
|
||
# 6. 命令行参数覆盖(最高优先级)
|
||
cmd_config = build_config_from_args(args)
|
||
config = deep_merge(config, cmd_config)
|
||
|
||
# 7. 处理 auth_token(优先从文件读取,否则生成新的)
|
||
import secrets
|
||
|
||
# 确定 token 文件路径
|
||
if getattr(sys, 'frozen', False):
|
||
# 打包环境:使用可执行文件所在目录(_MEIPASS 是临时解压目录,写入会丢失)
|
||
token_file = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'auth_token.txt')
|
||
else:
|
||
# 非打包:使用脚本所在目录
|
||
token_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'auth_token.txt')
|
||
|
||
# 尝试从文件读取
|
||
existing_token = None
|
||
if os.path.exists(token_file):
|
||
try:
|
||
with open(token_file, 'r') as f:
|
||
existing_token = f.read().strip()
|
||
if existing_token:
|
||
config['auth_token'] = existing_token
|
||
print(f" 已从文件加载 auth_token: {config['auth_token'][:16]}...")
|
||
except Exception as e:
|
||
print(f" 警告: 读取 auth_token 失败: {e}")
|
||
|
||
# 如果没有现有 token,生成新的
|
||
if not existing_token:
|
||
config['auth_token'] = secrets.token_hex(32)
|
||
print(f" 已生成新的 auth_token: {config['auth_token'][:16]}...")
|
||
|
||
# 保存新的 token 到文件
|
||
try:
|
||
with open(token_file, 'w') as f:
|
||
f.write(config['auth_token'])
|
||
print(f" 已保存 auth_token 到: {token_file}")
|
||
except Exception as e:
|
||
print(f" 警告: 保存 auth_token 失败: {e}")
|
||
|
||
print(f" 最终配置: {len(config)} 个顶层配置项")
|
||
|
||
# ==================== 系统兼容性检查 ====================
|
||
if args.check_compat:
|
||
print("\n[系统兼容性检查]")
|
||
compat = check_compatibility()
|
||
print(f"\n兼容状态: {'✓ 通过' if compat['compatible'] else '✗ 存在问题'}")
|
||
|
||
arch = ArchitectureDetector.get_architecture()
|
||
print(f"\n架构信息:")
|
||
print(f" - 平台: {arch['platform']}")
|
||
print(f" - 机器: {arch['machine']}")
|
||
print(f" - 架构: {arch['architecture']}")
|
||
|
||
recommended = ArchitectureDetector.get_recommended_config()
|
||
print(f"\n推荐配置:")
|
||
for k, v in recommended.items():
|
||
print(f" - {k}: {v}")
|
||
|
||
if compat['warnings']:
|
||
print(f"\n警告:")
|
||
for w in compat['warnings']:
|
||
print(f" ⚠ {w}")
|
||
|
||
if compat['errors']:
|
||
print(f"\n错误:")
|
||
for e in compat['errors']:
|
||
print(f" ✗ {e}")
|
||
|
||
sys.exit(0 if compat['compatible'] else 1)
|
||
|
||
# ==================== 低端设备优化 ====================
|
||
print("\n[设备检测与优化]")
|
||
|
||
# 检测架构
|
||
arch = ArchitectureDetector.get_architecture()
|
||
|
||
# 应用低端设备配置
|
||
preset = config.get('preset', 'auto')
|
||
low_mem_config = LowMemoryConfig(preset)
|
||
|
||
# 获取设备信息
|
||
device_info = low_mem_config.get_device_info()
|
||
|
||
# 显示设备信息
|
||
if device_info['total_ram_mb'] > 0:
|
||
print(f" [硬件配置]")
|
||
print(f" - 总内存: {device_info['total_ram_mb']:.0f} MB")
|
||
print(f" - 可用内存: {device_info['available_ram_mb']:.0f} MB ({100-device_info['percent_used']:.1f}% 可用)")
|
||
print(f" - CPU核心: {device_info['cpu_count']} 核心")
|
||
print(f" - 系统架构: {arch['machine']} ({arch['architecture']})")
|
||
else:
|
||
print(f" - 系统架构: {arch['machine']} ({arch['architecture']})")
|
||
|
||
print(f"\n [性能优化]")
|
||
print(f" - 优化模式: {preset}")
|
||
|
||
# 应用配置
|
||
if preset != 'auto':
|
||
status = low_mem_config.get_status()
|
||
print(f" - 预设方案: {status.get('description', preset)}")
|
||
|
||
config = low_mem_config.apply_to_config(config)
|
||
|
||
# 覆盖命令行参数
|
||
if args.memory_limit:
|
||
config['memory_limit'] = parse_size(args.memory_limit)
|
||
if args.workers > 0:
|
||
config['workers'] = args.workers
|
||
if args.chunk_size:
|
||
config['chunk_size'] = parse_size(args.chunk_size)
|
||
if args.buffer_size:
|
||
config['buffer_size'] = parse_size(args.buffer_size)
|
||
|
||
# 禁用可选功能
|
||
if args.disable_ws:
|
||
config['enable_ws'] = False
|
||
if args.disable_sse:
|
||
config['enable_sse'] = False
|
||
if args.disable_hash:
|
||
config['calculate_hash'] = False
|
||
|
||
# 启动内存管理器
|
||
memory_manager = MemoryManager({
|
||
'enabled': True,
|
||
'memory_limit': config.get('memory_limit', 512 * 1024 * 1024),
|
||
'gc_interval': config.get('gc_interval', 300), # 定时GC间隔
|
||
'enable_scheduled_gc': config.get('enable_scheduled_gc', True)
|
||
})
|
||
memory_manager.start()
|
||
config['_memory_manager'] = memory_manager
|
||
|
||
mem_status = memory_manager.get_status()
|
||
print(f" - 内存限制: {mem_status['memory_limit_mb']} MB")
|
||
print(f" - 工作进程: {config.get('workers', 1)}")
|
||
print(f" - 传输块: {config.get('chunk_size', 128 * 1024) // 1024} KB")
|
||
print(f" - GC间隔: {config.get('gc_interval', 300)}秒")
|
||
print(f" - 缓存大小: {config.get('max_cache_size', 0) // (1024*1024)} MB")
|
||
|
||
# 设置信号处理
|
||
signal.signal(signal.SIGINT, signal_handler)
|
||
signal.signal(signal.SIGTERM, signal_handler)
|
||
|
||
# 打印启动信息
|
||
print()
|
||
print("╔" + "═" * 58 + "╗")
|
||
print("║" + " " * 22 + "HYC下载站 v2.3" + " " * 22 + "║")
|
||
print("╚" + "═" * 58 + "╝")
|
||
|
||
print()
|
||
print("【服务器配置】")
|
||
print(f" ▶ 监听地址: {config.get('host')}:{config.get('port')}")
|
||
print(f" ▶ 文件目录: {os.path.abspath(config.get('base_dir', './downloads'))}")
|
||
print(f" ▶ API版本: {config.get('api_version', 'v2')}")
|
||
print(f" ▶ 认证方式: {config.get('auth_type', 'none')}")
|
||
max_upload = config.get('max_upload_size', '1G')
|
||
if isinstance(max_upload, str):
|
||
max_upload = parse_size(max_upload)
|
||
else:
|
||
max_upload = int(max_upload)
|
||
print(f" ▶ 最大上传: {max_upload // (1024*1024)} MB")
|
||
|
||
# 数据库状态
|
||
db_config = config.get('database', {})
|
||
db_enabled = db_config.get('enabled', True)
|
||
|
||
print()
|
||
print("【功能模块】")
|
||
print(f" {'●' if config.get('enable_monitor') else '○'} 系统监控", end='')
|
||
print(f" {'●' if config.get('enable_sync') else '○'} 镜像同步", end='')
|
||
print(f" {'●' if config.get('enable_mirrors') else '○'} 加速源")
|
||
print(f" {'●' if config.get('enable_ws') else '○'} WebSocket", end='')
|
||
print(f" {'●' if config.get('enable_sse') else '○'} SSE", end='')
|
||
print(f" {'●' if db_enabled else '○'} 数据库")
|
||
|
||
if db_enabled:
|
||
print(f" └── 类型: {db_config.get('type', 'sqlite')} | 同步间隔: {db_config.get('sync_interval', 60)}s")
|
||
|
||
print()
|
||
print("【性能参数】")
|
||
print(f" ▶ 最大线程: {config.get('max_workers', 10)}")
|
||
print(f" ▶ 工作进程: {config.get('workers', 1)}")
|
||
print(f" ▶ 内存限制: {config.get('memory_limit', 512*1024*1024) // (1024*1024)} MB")
|
||
print(f" ▶ 连接超时: {config.get('timeout', 30)}s")
|
||
|
||
print()
|
||
print("=" * 60)
|
||
|
||
# 初始化数据库
|
||
db = None
|
||
scheduler = None
|
||
if db_enabled:
|
||
print("\n[初始化数据库...]")
|
||
try:
|
||
db = init_database(config)
|
||
|
||
# 健康检查
|
||
health = db.health_check()
|
||
if health.get('healthy'):
|
||
print(f" ✓ 数据库连接成功 ({db_config.get('type', 'sqlite')})")
|
||
print(f" ✓ 表数量: {health.get('table_count', 0)}")
|
||
else:
|
||
print(f" ✗ 数据库健康检查失败: {health.get('error')}")
|
||
|
||
# 检查并更新Schema
|
||
if db.check_schema_version():
|
||
print(f" ✓ 数据库结构版本检查通过")
|
||
|
||
# 获取统计
|
||
db_stats = db.get_stats()
|
||
print(f"\n 数据库统计:")
|
||
print(f" - 文件记录: {db_stats['total_files']}")
|
||
print(f" - 同步记录: {db_stats['total_sync_records']}")
|
||
print(f" - 缓存记录: {db_stats['total_cache_records']}")
|
||
print(f" - 下载记录: {db_stats['total_download_records']}")
|
||
|
||
# 将数据库实例添加到配置中
|
||
config['_db_instance'] = db
|
||
|
||
except Exception as e:
|
||
print(f"\n ✗ 数据库初始化失败: {e}")
|
||
print(" ⚠ 服务器将继续运行,但不使用数据库功能")
|
||
|
||
# 初始化同步调度器(不使用数据库)
|
||
if config.get('enable_sync'):
|
||
_, scheduler, _ = init_database_sync(config)
|
||
scheduler.start()
|
||
print(" - 同步调度器已启动")
|
||
|
||
# ==================== 优雅关闭处理 ====================
|
||
import atexit
|
||
|
||
def cleanup():
|
||
"""服务器关闭时清理资源"""
|
||
print("\n正在关闭服务器...")
|
||
|
||
# 关闭数据库连接池
|
||
if db:
|
||
try:
|
||
db.engine.dispose()
|
||
print("✓ 数据库连接已关闭")
|
||
except Exception as e:
|
||
print(f"✗ 关闭数据库连接时出错: {e}")
|
||
|
||
# 停止同步调度器
|
||
if scheduler:
|
||
try:
|
||
scheduler.stop()
|
||
print("✓ 同步调度器已停止")
|
||
except Exception as e:
|
||
print(f"✗ 停止同步调度器时出错: {e}")
|
||
|
||
print("服务器已关闭")
|
||
|
||
# 注册关闭处理函数
|
||
atexit.register(cleanup)
|
||
|
||
# 设置服务器启动时间(用于计算运行时间)
|
||
config['start_time'] = time.time()
|
||
|
||
# 默认凭据警告
|
||
try:
|
||
if config.get('auth_type') == 'basic' and config.get('auth_pass') in (None, '', 'admin123'):
|
||
print("\n" + "!" * 60)
|
||
print("! 安全警告: 正在使用默认/空认证密码 (admin/admin123)")
|
||
print("! 请立即修改 settings.json 中的 auth_pass 或使用 --auth-pass 指定")
|
||
print("!" * 60)
|
||
if config.get('auth_type') == 'token' and config.get('auth_token') in (None, '', 'admin_token_123456'):
|
||
print("\n" + "!" * 60)
|
||
print("! 安全警告: 正在使用默认/空认证 token")
|
||
print("! 请立即修改 settings.json 中的 auth_token 或使用 --auth-token 指定")
|
||
print("!" * 60)
|
||
except Exception:
|
||
pass
|
||
|
||
# 创建并启动服务器
|
||
try:
|
||
server = MirrorServer(config)
|
||
if server.start():
|
||
server.serve_forever()
|
||
else:
|
||
print("服务器启动失败")
|
||
sys.exit(1)
|
||
except KeyboardInterrupt:
|
||
# 信号处理已触发优雅关闭(server.stop + atexit cleanup 已执行)
|
||
print("\n服务器已正常退出")
|
||
sys.exit(0)
|
||
except Exception as e:
|
||
print(f"错误: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|