Files
socks-manager/services/backup_service.py
T
Your Name 558bd8153f feat: 重构为 SOCKS5 代理管理平台 v2.0
架构:
- engine/server.py: asyncio SOCKS5 服务器引擎(含隧道/认证/限速)
- engine/instances.py: 多实例管理器(独立事件循环/热启动/热停止)
- services/user_service.py: 用户认证/流量统计/IP白名单/防爆破
- services/stats_service.py: 实时监控/流量趋势/连接追踪
- services/backup_service.py: 数据库备份与恢复
- api/v1.py: 完整 RESTful API
- web/routes.py: Web 管理面板(暗色 Bootstrap 5 主题)

功能覆盖:
 多实例 SOCKS5 管理(创建/启动/停止/重启/热加载)
 用户认证(无认证/账号密码/流量上限/限速/并发限制)
 IP 白名单/黑名单/防爆破
 实时仪表盘(CPU/内存/网络/活跃连接)
 流量统计(30天趋势图/用户排名/实例统计)
 活跃连接追踪
 审计日志(事件/用户/IP 筛选)
 数据库备份与恢复
 管理员密码保护
 16 项功能测试全部通过
2026-07-16 17:31:53 +08:00

109 lines
3.4 KiB
Python

"""备份与恢复服务。"""
import logging
import os
import shutil
import sqlite3
from datetime import datetime, timezone
from models import Backup
from database import db
log = logging.getLogger("socks.backup")
_BACKUP_DIR = None
def set_backup_dir(path):
global _BACKUP_DIR
_BACKUP_DIR = os.path.abspath(path)
os.makedirs(_BACKUP_DIR, exist_ok=True)
def get_backup_dir():
return _BACKUP_DIR or os.path.join(os.path.dirname(__file__), "..", "backups")
def create_backup(note=""):
"""创建数据库备份。"""
global _BACKUP_DIR
if not _BACKUP_DIR:
_BACKUP_DIR = get_backup_dir()
os.makedirs(_BACKUP_DIR, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"sm_backup_{ts}.db"
dest = os.path.join(_BACKUP_DIR, filename)
db_uri = db.engine.url.render_as_string(hide_password=False)
if db_uri.startswith("sqlite:///"):
src = db_uri.replace("sqlite:///", "", 1)
if not src.startswith("/"):
src = os.path.join(os.path.dirname(__file__), "..", src)
shutil.copy2(src, dest)
else:
# 其他数据库类型需要不同处理
log.warning("不支持的数据库类型备份: %s", db_uri)
return {"error": "不支持的数据库类型"}
size = os.path.getsize(dest)
with db.app.app_context():
b = Backup(filename=filename, path=dest, size=size, note=note)
db.session.add(b)
db.session.commit()
log.info("备份完成: %s (%.1f MB)", filename, size / 1024 / 1024)
return {"filename": filename, "path": dest, "size": size}
def list_backups():
"""列出所有备份。"""
with db.app.app_context():
backups = db.session.query(Backup).order_by(Backup.created_at.desc()).all()
return [{
"id": b.id,
"filename": b.filename,
"path": b.path,
"size_mb": round(b.size / 1024 / 1024, 1),
"created_at": b.created_at.isoformat(),
"note": b.note,
"exists": os.path.exists(b.path),
} for b in backups]
def restore_backup(backup_id):
"""从备份恢复。"""
with db.app.app_context():
b = db.session.query(Backup).get(backup_id)
if not b or not os.path.exists(b.path):
return {"error": "备份文件不存在"}
db_uri = db.engine.url.render_as_string(hide_password=False)
if not db_uri.startswith("sqlite:///"):
return {"error": "不支持的数据库类型"}
src = b.path
dest = db_uri.replace("sqlite:///", "", 1)
if not dest.startswith("/"):
dest = os.path.join(os.path.dirname(__file__), "..", dest)
# 先备份当前数据库
if os.path.exists(dest):
backup_current = f"{dest}.pre_restore_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(dest, backup_current)
shutil.copy2(src, dest)
log.info("从备份恢复: %s -> %s", b.filename, dest)
return {"ok": True, "restored_from": b.filename}
def cleanup_old_backups(keep=10):
"""清理旧备份,保留最近 N 份。"""
with db.app.app_context():
backups = db.session.query(Backup).order_by(Backup.created_at.desc()).all()
for b in backups[keep:]:
if os.path.exists(b.path):
os.remove(b.path)
db.session.delete(b)
log.info("删除旧备份: %s", b.filename)
db.session.commit()