feat(deployer): sync 前备份远程服务目录

rsync --delete 会清掉目标目录里的多余文件;先拷到 $HOME/bakeup/deployer-skill,失败则中止同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 23:45:45 +08:00
parent 130dcc36c5
commit 86ce602b28
5 changed files with 64 additions and 5 deletions
+57
View File
@@ -12,12 +12,16 @@ from __future__ import annotations
import argparse
import os
import re
import shlex
import shutil
import subprocess
import sys
from pathlib import Path
BAKEUP_KEEP = 5
BAKEUP_ROOT = "deployer-skill"
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
@@ -25,7 +29,60 @@ if str(_SCRIPT_DIR) not in sys.path:
from lib import DEFAULT_BASE_PATH, rsync_ssh_args, scp_base_args, service_info, ssh_cmd # noqa: E402
def _safe_bakeup_name(name: str) -> str:
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", name).strip("-.")
return cleaned or "service"
def _backup_remote(info: dict) -> int:
"""把远程服务目录拷到 $HOME/bakeup/deployer-skill/<name>/<timestamp>/,失败则中止 sync。"""
name = _safe_bakeup_name(info["name"])
remote_dir = info["remote_dir"]
script = f"""
set -eu
remote_dir={shlex.quote(remote_dir)}
name={shlex.quote(name)}
root={shlex.quote(BAKEUP_ROOT)}
keep={BAKEUP_KEEP}
if [ ! -d "$remote_dir" ]; then
echo "跳过备份: 远程目录不存在 $remote_dir"
exit 0
fi
if [ -z "$(find "$remote_dir" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
echo "跳过备份: 远程目录为空 $remote_dir"
exit 0
fi
stamp=$(date +%Y%m%d-%H%M%S)
base="$HOME/bakeup/$root/$name"
dest="$base/$stamp"
mkdir -p "$dest"
chmod 700 "$HOME/bakeup" "$HOME/bakeup/$root" "$base" "$dest"
cp -a "$remote_dir"/. "$dest"/
ls -1d "$base"/[0-9][0-9][0-9][0-9]* 2>/dev/null | sort -r | tail -n +"$((keep + 1))" | while IFS= read -r old; do
rm -rf "$old"
done
echo "已备份到 $dest (保留最近 $keep 份)"
"""
cmd = ssh_cmd(info, script)
print(f"备份远程目录 {info['node']}:{remote_dir}")
print(f"备份位置: $HOME/bakeup/{BAKEUP_ROOT}/{name}/<时间戳>/")
print("-" * 60)
try:
completed = subprocess.run(cmd, check=False)
except FileNotFoundError:
print("错误: ssh 命令未找到,无法备份")
return 1
if completed.returncode:
print(f"错误: 远程备份失败,退出码: {completed.returncode};已中止同步")
return completed.returncode
print("-" * 60)
return 0
def sync_directory(info: dict) -> int:
backup_rc = _backup_remote(info)
if backup_rc:
return backup_rc
if shutil.which("rsync"):
return _sync_rsync(info)
print("本机没有 rsync,改用 scp(不会删除远程多余文件)")