228 lines
9.0 KiB
Bash
Executable File
228 lines
9.0 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================
|
||
# dual-backup.sh — 双备份同步脚本
|
||
# 本机 git 版本控制 + 服务器 SMB 远程备份
|
||
# 用法: dual-backup.sh [push|pull|status]
|
||
# push : 本机 → 服务器(默认,增量同步)
|
||
# pull : 服务器 → 本机(恢复场景)
|
||
# status: 查看备份状态
|
||
# ============================================================
|
||
set -e
|
||
|
||
HOME_DIR="$HOME"
|
||
HERMES="$HOME_DIR/.hermes"
|
||
BACKUP_ROOT="/mnt/server-backup/xiaowei"
|
||
BACKUP_CONFIG="$BACKUP_ROOT/config"
|
||
BACKUP_SCRIPTS="$BACKUP_ROOT/scripts"
|
||
BACKUP_WORKFLOWS="$BACKUP_ROOT/workflows"
|
||
BACKUP_DAEMON="$BACKUP_ROOT/daemon"
|
||
BACKUP_SYSTEMD="$BACKUP_ROOT/systemd"
|
||
BACKUP_SNAPSHOTS="$BACKUP_ROOT/snapshots"
|
||
LOG="$HERMES/watchdog/dual-backup.log"
|
||
|
||
mkdir -p "$(dirname "$LOG")"
|
||
log() {
|
||
if [ "${QUIET:-0}" = "1" ]; then
|
||
# cron 静默模式:只写日志文件,不输出 stdout(no_agent 空输出=不推飞书)
|
||
echo "[$(date '+%H:%M:%S')] $*" >> "$LOG"
|
||
else
|
||
echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG"
|
||
fi
|
||
}
|
||
|
||
# 检查服务器挂载
|
||
# ⚠️ 2026-08-12 修复:家庭服务器不在同一局域网是常态(走 frp 域名),
|
||
# 离线静默跳过(exit 0),不当作错误上报。只有"挂载点在但同步失败"才报错。
|
||
check_mount() {
|
||
if ! mountpoint -q /mnt/server-backup; then
|
||
log "⚠️ 服务器未挂载,尝试连接..."
|
||
# 2026-09-03:mount cifs 在服务器 TCP 不可达时会长时间挂起(19:00 cron 曾超时 3600s)
|
||
# → 先快速探测可达性(ping 1 次 2s 超时),不可达直接走"离线常态"跳过,避免 mount 挂起
|
||
if ! ping -c 1 -W 2 192.168.188.11 >/dev/null 2>&1; then
|
||
log "ℹ️ 服务器不在局域网(常态),跳过本次备份"
|
||
return 2
|
||
fi
|
||
# -n: non-interactive,cron 环境无密码输入机会,立即失败不卡住
|
||
# || true: 失败是预期路径(服务器不在线),由下方 mountpoint 判断处理,避免 set -e 提前退出
|
||
sudo -n mount -t cifs //192.168.188.11/beifen /mnt/server-backup \
|
||
-o username=administrator,password=xue.2538,uid=1000,gid=1000,iocharset=utf8,file_mode=0755,dir_mode=0755,timeout=15 2>/dev/null || true
|
||
sleep 2
|
||
if ! mountpoint -q /mnt/server-backup; then
|
||
log "ℹ️ 服务器不在局域网(常态),跳过本次备份"
|
||
return 2
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# === 推送到服务器 ===
|
||
push_backup() {
|
||
log "📤 开始双备份推送..."
|
||
|
||
# 1. 本机 git 快照(无论服务器是否可达都做——本机备份是底线)
|
||
log " 本机快照..."
|
||
bash "$HERMES/scripts/config-protector.sh snapshot" 2>/dev/null || true
|
||
|
||
# 2. 检查服务器(if 条件中的命令不受 set -e 影响,且能拿到真实返回码)
|
||
if check_mount; then
|
||
: # 挂载成功,继续
|
||
else
|
||
local rc=$?
|
||
if [ "$rc" -eq 2 ]; then
|
||
# 离线常态:静默跳过(QUIET 下 stdout 为空 → cron 不推飞书)
|
||
log "ℹ️ 服务器不在局域网,跳过服务器同步(等电脑拿回家自动恢复)"
|
||
return 0
|
||
fi
|
||
log "❌ 跳过备份(服务器离线)"
|
||
return 1
|
||
fi
|
||
|
||
# 3. 同步核心文档
|
||
log " 同步配置..."
|
||
rsync -av --delete "$HERMES/SOUL.md" "$BACKUP_CONFIG/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/AGENTS.md" "$BACKUP_CONFIG/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/MEMORY.md" "$BACKUP_CONFIG/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/USER.md" "$BACKUP_CONFIG/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/CREATIVE.md" "$BACKUP_CONFIG/" 2>&1 | tail -1
|
||
|
||
# 4. 同步核心脚本
|
||
log " 同步脚本..."
|
||
rsync -av --delete "$HERMES/scripts/"*.py "$BACKUP_SCRIPTS/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/scripts/"*.sh "$BACKUP_SCRIPTS/" 2>&1 | tail -1
|
||
|
||
# 5. 同步工作流模板
|
||
log " 同步工作流..."
|
||
rsync -av --delete "$HERMES/workflows/"*.yaml "$BACKUP_WORKFLOWS/" 2>&1 | tail -1
|
||
|
||
# 6. 同步 daemon 状态
|
||
log " 同步 daemon..."
|
||
rsync -av --delete "$HERMES/daemon/solutions.json" "$BACKUP_DAEMON/" 2>&1 | tail -1
|
||
rsync -av --delete "$HERMES/daemon/context.json" "$BACKUP_DAEMON/" 2>&1 | tail -1
|
||
|
||
# 7. 同步 systemd 服务文件
|
||
log " 同步 systemd..."
|
||
rsync -av --delete "$HOME_DIR/.config/systemd/user/xiaowei-daemon.service" "$BACKUP_SYSTEMD/" 2>&1 | tail -1
|
||
rsync -av --delete "$HOME_DIR/.config/systemd/user/xiaowei-startup.service" "$BACKUP_SYSTEMD/" 2>&1 | tail -1
|
||
|
||
# 8. 同步 skills 目录(通过 git bundle,包含全部变更历史)
|
||
log " 同步 skills..."
|
||
if [ -d "$HERMES/.git" ]; then
|
||
cd "$HERMES"
|
||
git bundle create "$BACKUP_SNAPSHOTS/hermes-git-$(date '+%Y%m%d').bundle" --all 2>/dev/null || true
|
||
fi
|
||
|
||
# 9. 同步 cron 配置
|
||
log " 同步 cron..."
|
||
if [ -f "$HERMES/cron/jobs.json" ]; then
|
||
cp "$HERMES/cron/jobs.json" "$BACKUP_ROOT/cron-jobs.json"
|
||
log " cron配置已备份"
|
||
else
|
||
log " ⚠️ 无cron配置可备份"
|
||
fi
|
||
|
||
# 10. 同步织忆数据(LanceDB + SQLite图谱 + 真实数据)
|
||
log " 同步织忆数据..."
|
||
# 10a. LanceDB 数据
|
||
if [ -d "/var/lib/memoryweave" ]; then
|
||
tar czf "$BACKUP_SNAPSHOTS/zhiyi-lancedb-$(date '+%Y%m%d').tar.gz" \
|
||
-C /var/lib/memoryweave --exclude='*.lance' --exclude='lancedb.bak' \
|
||
. 2>/dev/null || log " ⚠️ 织忆LanceDB同步失败"
|
||
fi
|
||
# 10b. SQLite 真实数据(绕过软链接,直接从 ~/.hermes/ 取)
|
||
if [ -f "$HERMES/graph.db" ] || [ -f "$HERMES/memoryweave.db" ]; then
|
||
tar czf "$BACKUP_SNAPSHOTS/zhiyi-sqlite-$(date '+%Y%m%d').tar.gz" \
|
||
-C "$HERMES" graph.db memoryweave.db 2>/dev/null || log " ⚠️ 织忆SQLite同步失败"
|
||
log " 📦 织忆SQLite已备份"
|
||
fi
|
||
|
||
# 11. 定期快照(每天保留一份关键配置快照)
|
||
SNAPSHOT_NAME="snapshot-$(date '+%Y%m%d')"
|
||
if [ ! -d "$BACKUP_SNAPSHOTS/$SNAPSHOT_NAME" ]; then
|
||
mkdir -p "$BACKUP_SNAPSHOTS/$SNAPSHOT_NAME"
|
||
cp "$HERMES/SOUL.md" "$BACKUP_SNAPSHOTS/$SNAPSHOT_NAME/"
|
||
cp "$HERMES/daemon/solutions.json" "$BACKUP_SNAPSHOTS/$SNAPSHOT_NAME/" 2>/dev/null || true
|
||
log " 📸 每日快照: $SNAPSHOT_NAME"
|
||
fi
|
||
|
||
log "✅ 双备份推送完成"
|
||
# 服务器同步成功才输出(cron 收到确认,牧尘拿回家后能看到)
|
||
if [ "${QUIET:-0}" = "1" ]; then
|
||
echo "✅ 双备份推送完成(服务器已连接)"
|
||
fi
|
||
}
|
||
|
||
# === 从服务器恢复 ===
|
||
pull_backup() {
|
||
log "📥 从服务器恢复..."
|
||
|
||
if ! check_mount; then
|
||
log "❌ 跳过恢复(服务器离线)"
|
||
return 1
|
||
fi
|
||
|
||
# 检查备份是否存在
|
||
if [ ! -f "$BACKUP_CONFIG/SOUL.md" ]; then
|
||
log "❌ 服务器上无备份数据"
|
||
return 1
|
||
fi
|
||
|
||
# 反向同步(服务器 → 本机)
|
||
rsync -av --delete "$BACKUP_CONFIG/" "$HERMES/" 2>&1 | tail -1
|
||
rsync -av --delete "$BACKUP_SCRIPTS/" "$HERMES/scripts/" 2>&1 | tail -1
|
||
rsync -av --delete "$BACKUP_WORKFLOWS/" "$HERMES/workflows/" 2>&1 | tail -1
|
||
rsync -av "$BACKUP_DAEMON/solutions.json" "$HERMES/daemon/" 2>&1 | tail -1
|
||
|
||
log "✅ 恢复完成"
|
||
}
|
||
|
||
# === 查看状态 ===
|
||
show_status() {
|
||
echo "=== 双备份状态 ==="
|
||
echo ""
|
||
|
||
if mountpoint -q /mnt/server-backup; then
|
||
echo "🟢 服务器: 已连接"
|
||
echo " 路径: /mnt/server-backup/xiaowei"
|
||
echo " 空间: $(df -h /mnt/server-backup | awk 'NR==2 {print $3 "已用 / " $2 " (" $5 ")"}')"
|
||
echo ""
|
||
echo " 备份内容:"
|
||
for dir in config scripts workflows daemon systemd snapshots; do
|
||
count=$(find "/mnt/server-backup/xiaowei/$dir" -maxdepth 1 -type f 2>/dev/null | wc -l)
|
||
last=$(ls -lt "/mnt/server-backup/xiaowei/$dir" 2>/dev/null | head -2 | tail -1 | awk '{print $6, $7, $8}')
|
||
echo " $dir: $count 个文件 (最新: ${last:-无})"
|
||
done
|
||
else
|
||
echo "🔴 服务器: 未连接"
|
||
fi
|
||
|
||
echo ""
|
||
echo "=== 本机 git 状态 ==="
|
||
cd "$HERMES"
|
||
echo " 最近提交: $(git log --oneline -1 2>/dev/null || echo '无')"
|
||
echo " 未跟踪: $(git status --short 2>/dev/null | wc -l) 个"
|
||
echo " 稳定标记: $(git tag -l 'stable-*' 2>/dev/null | wc -l) 个"
|
||
|
||
echo ""
|
||
echo "=== Gitea ==="
|
||
curl -sf --max-time 3 http://192.168.123.11:3000/api/v1/repos/xiaoxue_admin/xiaowei-system 2>/dev/null \
|
||
| python3 -c "import json,sys; d=json.load(sys.stdin); print(f' 仓库: {d.get(\"name\",\"?\")} | 更新: {d.get(\"updated_at\",\"?\")[:10]}')" 2>/dev/null \
|
||
|| echo " 仓库: xiaowei-system (离线)"
|
||
}
|
||
|
||
|
||
# === 主入口 ===
|
||
case "${1:-push}" in
|
||
push)
|
||
push_backup
|
||
;;
|
||
pull)
|
||
pull_backup
|
||
;;
|
||
status)
|
||
show_status
|
||
;;
|
||
*)
|
||
echo "用法: dual-backup.sh [push|pull|status]"
|
||
;;
|
||
esac
|