xiaowei-system/scripts/dual-backup.sh

228 lines
9.0 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 静默模式:只写日志文件,不输出 stdoutno_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-03mount 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-interactivecron 环境无密码输入机会,立即失败不卡住
# || 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