From 7969abcfe4d331adbb6c80027835244b39ffa6b9 Mon Sep 17 00:00:00 2001 From: mango Date: Mon, 2 Feb 2026 16:19:38 +0800 Subject: [PATCH] =?UTF-8?q?v3.7:=20=E6=B7=BB=E5=8A=A0=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=A8=A1=E5=BC=8F=20-=20=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E9=80=89=E6=8B=A9=E5=AE=8C=E6=95=B4=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E6=88=96=E4=BB=85=E6=81=A2=E5=A4=8D=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vps-snapshot.sh | 127 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 6 deletions(-) diff --git a/vps-snapshot.sh b/vps-snapshot.sh index efbde19..61d300a 100755 --- a/vps-snapshot.sh +++ b/vps-snapshot.sh @@ -1,7 +1,7 @@ #!/bin/bash #=============================================================================== -# VPS 快照备份脚本 v3.6.6 +# VPS 快照备份脚本 v3.7.6 # 支持: Ubuntu, Debian, CentOS, Alpine # 功能: 智能识别应用 + Docker迁移 + 数据备份 + Telegram通知 #=============================================================================== @@ -19,7 +19,7 @@ LOG_FILE="/var/log/vps-snapshot.log" print_banner() { echo -e "${BLUE}" echo "╔═══════════════════════════════════════════════════════════╗" - echo "║ VPS 快照备份脚本 v3.6 ║" + echo "║ VPS 快照备份脚本 v3.7 ║" echo "║ 智能识别 + Docker迁移 + 数据备份 ║" echo "╚═══════════════════════════════════════════════════════════╝" echo -e "${NC}" @@ -498,11 +498,20 @@ create_snapshot() { # 检测应用并备份 detect_apps > /dev/null - # Docker 数据 # Docker 数据 - 导出到临时目录 local tmp_dir="/tmp/snapshot_$$" mkdir -p "$tmp_dir" + # 保存已安装包列表(用于完整恢复) + log "保存系统包列表..." + if command -v dpkg &>/dev/null; then + dpkg --get-selections | grep -v deinstall | awk '{print $1}' > "$tmp_dir/installed-packages.txt" + elif command -v rpm &>/dev/null; then + rpm -qa --qf '%{NAME}\n' > "$tmp_dir/installed-packages.txt" + elif command -v apk &>/dev/null; then + apk list -I 2>/dev/null | awk -F' ' '{print $1}' | sed 's/-[0-9].*//' > "$tmp_dir/installed-packages.txt" + fi + if command -v docker &>/dev/null && docker info &>/dev/null; then docker_export "$tmp_dir" fi @@ -579,12 +588,118 @@ do_restore_local() { [ ! -f "$snap_dir/$snap_file" ] && { error "文件不存在"; return 1; } - log "🔄 恢复快照: $snap_file" + # 选择恢复模式 + echo "" + info "选择恢复模式:" + echo " 1) 完整恢复 (恢复到快照时状态,删除后来安装的软件)" + echo " 2) 仅恢复数据 (只恢复Docker和应用数据,保留现有系统)" + echo "" + read -p "请选择 [1-2]: " restore_mode + + case "$restore_mode" in + 1) do_full_restore "$snap_dir/$snap_file" ;; + 2) do_data_restore "$snap_dir/$snap_file" ;; + *) error "无效选择"; return 1 ;; + esac +} + +# 完整恢复 - 恢复到快照时的完整状态 +do_full_restore() { + local snap_file="$1" + log "🔄 完整恢复: $(basename $snap_file)" # 解压到临时目录 local tmp_dir="/tmp/restore_$$" mkdir -p "$tmp_dir" - tar -xzf "$snap_dir/$snap_file" -C "$tmp_dir" + tar -xzf "$snap_file" -C "$tmp_dir" + + # 读取快照时的已安装包列表 + if [ -f "$tmp_dir/installed-packages.txt" ]; then + log "恢复系统包状态..." + + # 获取当前已安装的包 + local current_pkgs="/tmp/current_pkgs_$$.txt" + if command -v dpkg &>/dev/null; then + dpkg --get-selections | grep -v deinstall | awk '{print $1}' > "$current_pkgs" + elif command -v rpm &>/dev/null; then + rpm -qa --qf '%{NAME}\n' > "$current_pkgs" + elif command -v apk &>/dev/null; then + apk list -I 2>/dev/null | awk -F' ' '{print $1}' | sed 's/-[0-9].*//' > "$current_pkgs" + fi + + # 找出快照后新安装的包 + if [ -f "$current_pkgs" ]; then + local new_pkgs=$(comm -23 <(sort "$current_pkgs") <(sort "$tmp_dir/installed-packages.txt")) + if [ -n "$new_pkgs" ]; then + log "删除快照后安装的软件包..." + echo "$new_pkgs" | head -20 + [ $(echo "$new_pkgs" | wc -l) -gt 20 ] && echo "... 等共 $(echo "$new_pkgs" | wc -l) 个包" + + if command -v apt-get &>/dev/null; then + echo "$new_pkgs" | xargs apt-get purge -y --auto-remove 2>/dev/null || true + elif command -v yum &>/dev/null; then + echo "$new_pkgs" | xargs yum remove -y 2>/dev/null || true + elif command -v apk &>/dev/null; then + echo "$new_pkgs" | xargs apk del 2>/dev/null || true + fi + fi + rm -f "$current_pkgs" + fi + fi + + # 停止并删除所有Docker容器 + if command -v docker &>/dev/null; then + log "清理现有 Docker 环境..." + docker stop $(docker ps -aq) 2>/dev/null || true + docker rm $(docker ps -aq) 2>/dev/null || true + docker system prune -af 2>/dev/null || true + fi + + # 导入Docker镜像 + if [ -f "$tmp_dir/docker-images.tar.gz" ]; then + log "导入 Docker 镜像..." + gunzip -c "$tmp_dir/docker-images.tar.gz" | docker load + fi + + # 恢复Docker volumes + if [ -f "$tmp_dir/docker-volumes.tar.gz" ]; then + log "恢复 Docker Volumes..." + tar -xzf "$tmp_dir/docker-volumes.tar.gz" -C /var/lib/docker/volumes/ 2>/dev/null || true + fi + + # 恢复docker-compose文件 + if [ -f "$tmp_dir/docker-compose.yml" ]; then + cp "$tmp_dir/docker-compose.yml" /root/ + fi + if [ -f "$tmp_dir/.env" ]; then + cp "$tmp_dir/.env" /root/ + fi + + # 恢复应用数据 + if ls "$tmp_dir"/app-data_*.tar.gz &>/dev/null; then + log "恢复应用数据..." + tar -xzf "$tmp_dir"/app-data_*.tar.gz -C / 2>/dev/null || true + fi + + # 启动Docker容器 + if [ -f /root/docker-compose.yml ]; then + log "启动 Docker 容器..." + cd /root && docker compose up -d 2>/dev/null || docker-compose up -d 2>/dev/null || true + fi + + rm -rf "$tmp_dir" + log "✅ 完整恢复完成" +} + +# 仅恢复数据 - 保留现有系统 +do_data_restore() { + local snap_file="$1" + log "🔄 恢复数据: $(basename $snap_file)" + + # 解压到临时目录 + local tmp_dir="/tmp/restore_$$" + mkdir -p "$tmp_dir" + tar -xzf "$snap_file" -C "$tmp_dir" # 导入Docker if [ -f "$tmp_dir/docker-images.tar.gz" ]; then @@ -599,7 +714,7 @@ do_restore_local() { fi rm -rf "$tmp_dir" - log "✅ 恢复完成" + log "✅ 数据恢复完成" } #===============================================================================