63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
"""每日 02:00 数据库备份 job —— 包装 backend/scripts/pg_backup.sh。
|
||
|
||
独立脚本可手动/CI 直跑;系统调度经此包装,保证与脚本同一份备份逻辑与轮转策略。
|
||
Windows 下须显式定位 Git Bash(PATH 里的 bash 可能是 WSL shim,mount/PATH 均不匹配),
|
||
脚本路径统一转 MSYS /d/... 格式传给子进程。
|
||
"""
|
||
|
||
import asyncio
|
||
import logging
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# backend/app/utils/backup_job.py → parents[2] = backend/
|
||
_SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "pg_backup.sh"
|
||
|
||
_GIT_BASH_CANDIDATES = (
|
||
Path(r"C:\Program Files\Git\bin\bash.exe"),
|
||
Path(r"C:\Program Files\Git\usr\bin\bash.exe"),
|
||
)
|
||
|
||
|
||
def _find_git_bash() -> str | None:
|
||
for cand in _GIT_BASH_CANDIDATES:
|
||
if cand.exists():
|
||
return str(cand)
|
||
return shutil.which("bash")
|
||
|
||
|
||
def _msys_path(p: Path) -> str:
|
||
"""D:/dpb/... → /d/dpb/...(Git Bash 挂载约定)。"""
|
||
s = p.as_posix()
|
||
if len(s) >= 2 and s[1] == ":":
|
||
return f"/{s[0].lower()}{s[2:]}"
|
||
return s
|
||
|
||
|
||
async def run_daily_backup() -> bool:
|
||
"""每日备份:调用 pg_backup.sh(独立脚本,pg_dump 自定义格式 + N 天轮转)。"""
|
||
script = _SCRIPT
|
||
if not script.exists():
|
||
alt = Path.cwd() / "scripts" / "pg_backup.sh"
|
||
script = alt if alt.exists() else script
|
||
if not script.exists():
|
||
logger.error(f"备份脚本不存在: {script}")
|
||
return False
|
||
git_bash = _find_git_bash()
|
||
if not git_bash:
|
||
logger.error("未找到 Git Bash(C:/Program Files/Git/bin/bash.exe)")
|
||
return False
|
||
proc = await asyncio.create_subprocess_exec(
|
||
git_bash, _msys_path(script),
|
||
stdout=asyncio.subprocess.PIPE,
|
||
stderr=asyncio.subprocess.PIPE,
|
||
)
|
||
stdout, stderr = await proc.communicate()
|
||
if proc.returncode == 0:
|
||
logger.info(f"每日备份完成: {stdout.decode().strip()}")
|
||
return True
|
||
logger.error(f"每日备份失败(rc={proc.returncode}): {stderr.decode()[:500]}")
|
||
return False
|