原因:
腾讯 VPS 不支持“快照下载”,还有快照限制数量:1
相关服务:香港站群服务器
如何在Ubuntu 24 VPS上制作快照并复制到本地运行的问题。
在 Ubuntu 24 VPS上制作磁盘镜像
DD 工具
用于低级别的复制和转换数据。
使用前需要注意:
- 有足够的空间存储镜像
- 磁盘设备正确被使用
- 压缩镜像以节省空间
- 镜像下载到本地
- 停止VPS上的除 SSH 网络连接 DNS,以外的所有服务
设计思路
- 使用 dd 工具备份 VPS 硬盘
- 备份生成 tar.gz 文件
- 在备份前从列表中“停止”不必要的服务,只保留 SSH,DNS 还有 网络连接
- 备份完成后,启动“停止”的服务,恢复正常
- 因为 Google Drive 挂载 (google-drive-ocamlfuse) 是独立硬盘,要检测。
- 运行前检测磁盘空间。
完整 Script: vps-backup.sh
#!/bin/bash
# VPS disk image backup script by Dave on 20Apirl2025
# Version 0.6
# Purpose: Stop services, create a complete system backup, then restore services
# Usage: sudo bash vps-backup.sh [output file path]
# Check if running with root privileges
if [ "$EUID" -ne 0 ]; then
echo "Please run this script with root privileges"
exit 1
fi
# Check parameters
if [ "$#" -lt 1 ]; then
echo "Usage: $0 [output file path]"
echo "Example: $0 /root/vps-backup.tar.gz"
exit 1
fi
OUTPUT_FILE="$1"
OUTPUT_DIR=$(dirname "$OUTPUT_FILE")
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
LOG_FILE="${OUTPUT_DIR}/vps_backup_${TIMESTAMP}.log"
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
# Define services to stop
SERVICES_TO_STOP=(
"mariadb.service"
"nginx.service"
"dovecot.service"
"php8.3-fpm.service"
"v2ray.service"
"[email protected]"
"tailscaled.service"
"google-drive.service"
"cron.service"
)
# Check Google Drive mount
GOOGLE_DRIVE_MOUNTED=false
if mount | grep -q "google-drive-ocamlfuse"; then
GOOGLE_DRIVE_MOUNTED=true
# Add Google Drive service to stop list if not already included
if ! echo "${SERVICES_TO_STOP[@]}" | grep -q "google-drive.service"; then
SERVICES_TO_STOP+=("google-drive.service")
fi
fi
# Function to log messages
log() {
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a "$LOG_FILE"
}
# Function to stop services
stop_services() {
log "Starting to stop services..."
for service in "${SERVICES_TO_STOP[@]}"; do
if systemctl is-active --quiet "$service"; then
log "Stopping service: $service"
systemctl stop "$service"
if [ $? -eq 0 ]; then
log "Service $service stopped successfully"
else
log "Warning: Unable to stop service $service"
fi
else
log "Service $service is already stopped"
fi
done
log "All specified services have been stopped"
# Execute sync to ensure data is written to disk
log "Executing sync command to ensure data is written to disk..."
sync
log "Sync completed"
}
# Function to start services
start_services() {
log "Starting to start services..."
# Start services in reverse order
for ((i=${#SERVICES_TO_STOP[@]}-1; i>=0; i--)); do
service="${SERVICES_TO_STOP[$i]}"
log "Starting service: $service"
systemctl start "$service"
if [ $? -eq 0 ]; then
log "Service $service started successfully"
else
log "Warning: Unable to start service $service"
fi
done
log "All services have been restored"
}
# Function to get disk information
get_disk_info() {
log "Getting disk information..."
# Find system partition
ROOT_PART=$(findmnt -n -o SOURCE /)
log "Root partition: $ROOT_PART"
if [[ "$ROOT_PART" == /dev/mapper/* ]]; then
# Handle LVM volumes
log "LVM volume detected, using lvdisplay for information"
DISK_INFO=$(lvdisplay | grep "LV Path" | awk '{print $3}')
ROOT_DISK="$ROOT_PART"
else
# For regular partitions
ROOT_DISK="$ROOT_PART"
DISK_DEV=$(echo "$ROOT_PART" | sed 's/[0-9]*$//')
DISK_INFO=$(lsblk -n -o NAME,SIZE,TYPE | grep "disk" | head -n 1)
log "Derived disk device: $DISK_DEV (not used)"
fi
log "Using system partition: $ROOT_DISK"
log "Disk information: $DISK_INFO"
# Return the actual available device path
echo "$ROOT_DISK"
}
# Function to check disk space
check_disk_space() {
local source_disk="$1"
log "Checking disk space..."
# Get disk or partition size
if [ -b "$source_disk" ]; then
# If it's a block device, use blockdev
DISK_SIZE=$(blockdev --getsize64 "$source_disk" 2>/dev/null)
# If blockdev fails, try to get size from df
if [ -z "$DISK_SIZE" ] || [ "$DISK_SIZE" -eq 0 ]; then
log "Unable to get size using blockdev, attempting to use df..."
local mount_point="/"
if [ -n "$source_disk" ]; then
# If it's a partition, find its mount point
mount_point=$(findmnt -n -o TARGET "$source_disk" 2>/dev/null || echo "/")
fi
DISK_SIZE=$(df -B1 "$mount_point" | awk 'NR==2 {print $3}') # Use used space as estimate
fi
else
# If not a block device, use df to get root partition size as an estimate
log "Warning: $source_disk is not a block device, using root partition size as estimate"
DISK_SIZE=$(df -B1 / | awk 'NR==2 {print $3}') # Use used space as estimate
fi
# Check available space in output directory
AVAILABLE_SPACE=$(df -B1 "$OUTPUT_DIR" | awk 'NR==2 {print $4}')
log "Size of data to backup: $(numfmt --to=iec-i --suffix=B $DISK_SIZE)"
log "Available space in output directory: $(numfmt --to=iec-i --suffix=B $AVAILABLE_SPACE)"
# Consider extra metadata and safety margin, needs 1.1 times the space
REQUIRED_SPACE=$(echo "$DISK_SIZE * 1.1" | bc | cut -d. -f1)
log "Recommended required space: $(numfmt --to=iec-i --suffix=B $REQUIRED_SPACE)"
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
log "Error: Insufficient disk space"
log "Need at least $(numfmt --to=iec-i --suffix=B $REQUIRED_SPACE) of available space"
echo "Error: Insufficient disk space"
echo "Need at least $(numfmt --to=iec-i --suffix=B $REQUIRED_SPACE) of available space"
return 1
else
log "Disk space check passed"
return 0
fi
}
# Function to check and handle Google Drive mount
handle_google_drive() {
log "Checking Google Drive mount..."
if [ "$GOOGLE_DRIVE_MOUNTED" = true ]; then
log "Google Drive mount detected"
# If output directory is on Google Drive, warn user
if [[ "$OUTPUT_FILE" == *"/ggdrive/"* ]] || mount | grep -q "google-drive-ocamlfuse.*$OUTPUT_DIR"; then
log "Warning: Output directory is on a Google Drive mount point"
log "This may cause slow or unstable backup"
# Check available space on Google Drive
GD_AVAILABLE_SPACE=$(df -B1 "$OUTPUT_DIR" | awk 'NR==2 {print $4}')
log "Google Drive available space: $(numfmt --to=iec-i --suffix=B $GD_AVAILABLE_SPACE)"
# Prompt user to confirm
echo "Warning: You are attempting to save the backup to Google Drive"
echo "Available space: $(numfmt --to=iec-i --suffix=B $GD_AVAILABLE_SPACE)"
echo "This may cause slow or unstable backup"
read -p "Continue? (y/n): " answer
if [[ "$answer" != "y" && "$answer" != "Y" ]]; then
log "User chose to terminate backup"
exit 1
fi
log "User confirmed to continue backup to Google Drive"
else
log "Output directory is not on Google Drive, will unmount Google Drive normally"
fi
# Stop Google Drive service
log "Stopping Google Drive service..."
if systemctl is-active --quiet "google-drive.service"; then
systemctl stop "google-drive.service"
log "Google Drive service stopped"
fi
# Unmount Google Drive
log "Unmounting Google Drive mount points..."
local gd_mount_points=$(mount | grep "google-drive-ocamlfuse" | awk '{print $3}')
for mount_point in $gd_mount_points; do
log "Unmounting: $mount_point"
fusermount -u "$mount_point"
if [ $? -eq 0 ]; then
log "Successfully unmounted $mount_point"
else
log "Warning: Unable to unmount $mount_point"
fi
done
else
log "No Google Drive mount detected"
fi
}
# Function to restore Google Drive mount
restore_google_drive() {
if [ "$GOOGLE_DRIVE_MOUNTED" = true ]; then
log "Restoring Google Drive service..."
systemctl start "google-drive.service"
if [ $? -eq 0 ]; then
log "Google Drive service restored"
else
log "Warning: Unable to restore Google Drive service"
fi
fi
}
# Function to create system backup
create_backup() {
SOURCE_DISK="$1"
log "Starting to create system backup..."
log "System root partition: $SOURCE_DISK"
log "Target file: $OUTPUT_FILE"
# Determine root directory mount point
ROOT_MOUNT=$(findmnt -n -o TARGET "$SOURCE_DISK" 2>/dev/null || echo "/")
log "Root directory mount point: $ROOT_MOUNT"
# Ensure output directory exists
OUTPUT_DIR=$(dirname "$OUTPUT_FILE")
mkdir -p "$OUTPUT_DIR"
# Modify extension to tar.gz
TAR_FILE="${OUTPUT_FILE%.*}.tar.gz"
log "Modified output file to: $TAR_FILE"
# Use tar to create filesystem backup
log "Starting tar backup execution..."
log "Excluding temporary directories and mount points..."
# Create exclusion list
EXCLUDE_LIST=()
EXCLUDE_LIST+=("--exclude=$TAR_FILE")
EXCLUDE_LIST+=("--exclude=/proc")
EXCLUDE_LIST+=("--exclude=/sys")
EXCLUDE_LIST+=("--exclude=/dev")
EXCLUDE_LIST+=("--exclude=/run")
EXCLUDE_LIST+=("--exclude=/mnt")
EXCLUDE_LIST+=("--exclude=/media")
EXCLUDE_LIST+=("--exclude=/tmp")
# Check if /home mount point exists, may be using separate partition
if findmnt /home >/dev/null 2>&1; then
log "Detected /home as separate mount point, will backup separately"
EXCLUDE_LIST+=("--exclude=/home")
HOME_SEPARATE=true
else
HOME_SEPARATE=false
fi
# Execute root filesystem backup
log "Starting backup of root filesystem..."
tar -czf "$TAR_FILE" "${EXCLUDE_LIST[@]}" --one-file-system -C "$ROOT_MOUNT" .
TAR_STATUS=$?
if [ $TAR_STATUS -eq 0 ]; then
log "Root filesystem backup successful: $TAR_FILE"
# Backup home directory (if separate partition)
if [ "$HOME_SEPARATE" = true ]; then
HOME_TAR="${OUTPUT_FILE%.*}_home.tar.gz"
log "Starting backup of /home partition to: $HOME_TAR"
tar -czf "$HOME_TAR" --one-file-system -C /home .
HOME_TAR_STATUS=$?
if [ $HOME_TAR_STATUS -eq 0 ]; then
log "/home partition backup successful: $HOME_TAR"
else
log "Warning: /home partition backup failed, status code: $HOME_TAR_STATUS"
fi
fi
# Backup successful, update output file path
OUTPUT_FILE="$TAR_FILE"
return 0
else
log "Error: Backup failed, status code: $TAR_STATUS"
return 1
fi
}
# Calculate backup information
calculate_backup_info() {
if [ -f "$OUTPUT_FILE" ]; then
# Calculate backup file size
BACKUP_SIZE=$(stat -c %s "$OUTPUT_FILE")
log "Backup file size: $(numfmt --to=iec-i --suffix=B $BACKUP_SIZE)"
# Calculate MD5 checksum
log "Calculating MD5 checksum..."
MD5SUM=$(md5sum "$OUTPUT_FILE" | awk '{print $1}')
log "MD5 checksum: $MD5SUM"
echo "$MD5SUM" > "${OUTPUT_FILE}.md5"
log "MD5 checksum saved to ${OUTPUT_FILE}.md5"
return 0
else
log "Error: Backup file does not exist"
return 1
fi
}
# Main execution flow
main() {
log "======= VPS Backup Started ======="
log "Timestamp: $TIMESTAMP"
# Get disk information
SOURCE_DISK=$(get_disk_info)
if [ -z "$SOURCE_DISK" ]; then
log "Error: Unable to determine a valid source device"
log "Please ensure the script has sufficient permissions to access system partitions"
exit 1
fi
# Check disk space
check_disk_space "$SOURCE_DISK"
if [ $? -ne 0 ]; then
log "Backup terminated due to insufficient disk space"
exit 1
fi
# Handle Google Drive mount
handle_google_drive
# Stop services
stop_services
# Create backup
create_backup "$SOURCE_DISK"
BACKUP_STATUS=$?
# Restore services
start_services
# Restore Google Drive mount
restore_google_drive
# Calculate backup information
if [ $BACKUP_STATUS -eq 0 ]; then
log "Backup created successfully, calculating backup information..."
calculate_backup_info
fi
log "======= VPS Backup Completed ======="
# Display final results
if [ $BACKUP_STATUS -eq 0 ]; then
log "Backup completed successfully!"
log "Backup file: $OUTPUT_FILE"
echo ""
echo "Backup completed successfully!"
echo "Backup file: $OUTPUT_FILE"
echo "Log file: $LOG_FILE"
else
log "Errors occurred during backup, please check log file: $LOG_FILE"
echo ""
echo "Errors occurred during backup, please check log file: $LOG_FILE"
fi
}
# Execute main function
main
演示:
root@jpt:~/scripts# vi vps-backup.sh
root@jpt:~/scripts# sudo ./vps-backup.sh /path/to/output/vps-backup.img
[2025-04-21 11:50:54] ======= VPS备份开始 =======
[2025-04-21 11:50:54] 时间戳: 20250421_115054
[2025-04-21 11:50:54] 检查磁盘空间...
[2025-04-21 11:50:54] 警告: [2025-04-21 11:50:54] 正在获取磁盘信息...
[2025-04-21 11:50:54] 根分区: /dev/vda2
[2025-04-21 11:50:54] 推导出的磁盘设备: /dev/vda (不使用)
[2025-04-21 11:50:54] 使用系统分区: /dev/vda2
[2025-04-21 11:50:54] 磁盘信息: vda 50G disk
/dev/vda2 不是块设备,使用根分区大小作为估计
[2025-04-21 11:50:54] 需要备份的数据大小: 9.9GiB
[2025-04-21 11:50:54] 输出目录可用空间: 38GiB
[2025-04-21 11:50:54] 建议的所需空间: 11GiB
[2025-04-21 11:50:54] 磁盘空间检查通过
[2025-04-21 11:50:54] 检查Google Drive挂载...
[2025-04-21 11:50:54] 检测到Google Drive挂载
[2025-04-21 11:50:54] 输出目录不在Google Drive上,将正常卸载Google Drive
[2025-04-21 11:50:54] 停止Google Drive服务...
[2025-04-21 11:50:55] Google Drive服务已停止
[2025-04-21 11:50:55] 卸载Google Drive挂载点...
[2025-04-21 11:50:55] 开始停止服务...
[2025-04-21 11:50:55] 停止服务: mariadb.service
[2025-04-21 11:50:56] 服务 mariadb.service 已成功停止
[2025-04-21 11:50:56] 停止服务: nginx.service
[2025-04-21 11:50:56] 服务 nginx.service 已成功停止
[2025-04-21 11:50:56] 停止服务: dovecot.service
[2025-04-21 11:50:57] 服务 dovecot.service 已成功停止
[2025-04-21 11:50:57] 停止服务: php8.3-fpm.service
[2025-04-21 11:50:57] 服务 php8.3-fpm.service 已成功停止
[2025-04-21 11:50:57] 停止服务: v2ray.service
[2025-04-21 11:50:57] 服务 v2ray.service 已成功停止
[2025-04-21 11:50:57] 停止服务: [email protected]
[2025-04-21 11:50:57] 服务 [email protected] 已成功停止
[2025-04-21 11:50:57] 停止服务: tailscaled.service
[2025-04-21 11:50:58] 服务 tailscaled.service 已成功停止
[2025-04-21 11:50:58] 服务 google-drive.service 已经是停止状态
[2025-04-21 11:50:58] 停止服务: cron.service
[2025-04-21 11:50:58] 服务 cron.service 已成功停止
[2025-04-21 11:50:58] 所有指定服务已停止
[2025-04-21 11:50:58] 执行sync命令以确保数据写入磁盘...
[2025-04-21 11:50:58] sync完成
[2025-04-21 11:50:58] 开始创建系统备份...
[2025-04-21 11:50:58] 系统根分区: [2025-04-21 11:50:54] 正在获取磁盘信息...
[2025-04-21 11:50:54] 根分区: /dev/vda2
[2025-04-21 11:50:54] 推导出的磁盘设备: /dev/vda (不使用)
[2025-04-21 11:50:54] 使用系统分区: /dev/vda2
[2025-04-21 11:50:54] 磁盘信息: vda 50G disk
/dev/vda2
[2025-04-21 11:50:58] 目标文件: /path/to/output/vps-backup.img
[2025-04-21 11:50:58] 根目录挂载点: /
[2025-04-21 11:50:58] 修改输出文件为: /path/to/output/vps-backup.tar.gz
[2025-04-21 11:50:58] 开始tar备份执行...
[2025-04-21 11:50:58] 排除临时目录和挂载点...
[2025-04-21 11:50:58] 开始备份根文件系统...
tar: ./var/spool/postfix/dev/log: socket ignored
tar: ./var/spool/postfix/private/dovecot-lmtp: socket ignored
tar: ./var/spool/postfix/private/auth: socket ignored
tar: ./var/snap/lxd/common/lxd-user/unix.socket: socket ignored
tar: ./var/snap/lxd/common/lxd/unix.socket: socket ignored
[2025-04-21 12:00:33] 根文件系统备份成功: /path/to/output/vps-backup.tar.gz
[2025-04-21 12:00:33] 开始启动服务...
[2025-04-21 12:00:33] 启动服务: cron.service
[2025-04-21 12:00:33] 服务 cron.service 已成功启动
[2025-04-21 12:00:33] 启动服务: google-drive.service
[2025-04-21 12:00:33] 服务 google-drive.service 已成功启动
[2025-04-21 12:00:33] 启动服务: tailscaled.service
[2025-04-21 12:00:33] 服务 tailscaled.service 已成功启动
[2025-04-21 12:00:33] 启动服务: [email protected]
[2025-04-21 12:00:33] 服务 [email protected] 已成功启动
[2025-04-21 12:00:33] 启动服务: v2ray.service
[2025-04-21 12:00:33] 服务 v2ray.service 已成功启动
[2025-04-21 12:00:33] 启动服务: php8.3-fpm.service
[2025-04-21 12:00:34] 服务 php8.3-fpm.service 已成功启动
[2025-04-21 12:00:34] 启动服务: dovecot.service
[2025-04-21 12:00:34] 服务 dovecot.service 已成功启动
[2025-04-21 12:00:34] 启动服务: nginx.service
[2025-04-21 12:00:34] 服务 nginx.service 已成功启动
[2025-04-21 12:00:34] 启动服务: mariadb.service
[2025-04-21 12:00:35] 服务 mariadb.service 已成功启动
[2025-04-21 12:00:35] 所有服务已恢复
[2025-04-21 12:00:35] 恢复Google Drive服务...
[2025-04-21 12:00:35] Google Drive服务已恢复
[2025-04-21 12:00:35] 备份创建成功,计算备份信息...
[2025-04-21 12:00:35] 备份文件大小: 4.7GiB
[2025-04-21 12:00:35] 计算MD5校验和...
[2025-04-21 12:01:03] MD5校验和: 55868c61518c9801a0ccdc79024bd3d7
[2025-04-21 12:01:03] MD5校验和已保存到 /path/to/output/vps-backup.tar.gz.md5
[2025-04-21 12:01:03] ======= VPS备份完成 =======
[2025-04-21 12:01:03] 备份成功完成!
[2025-04-21 12:01:03] 备份文件: /path/to/output/vps-backup.tar.gz备份成功完成!
备份文件: /path/to/output/vps-backup.tar.gz
日志文件: /path/to/output/vps_backup_20250421_115054.log
root@jpt:~/scripts# ll /path/to/output/vps-backup.tar.gz
-rw-r--r-- 1 root root 5044560222 Apr 21 12:00 /path/to/output/vps-backup.tar.gz
root@jpt:~/scripts#

可以看到 运行时 google drive 是umount, 结束后恢复。

目录是 +- 10GB,备份后是 5GB
传输到网盘
1. 之前有提到 google drive
参考:
< OS 有关 > 利用 google-drive-ocamlfuse 工具,在 Ubuntu 24 系统上 加载 Google DRIVE 网盘_google网盘 ubuntu-CSDN博客
2. 使用 bdgo.sh
参考: < OS 有关> BaiduPCS-Go 程序的 菜单脚本 Script: BaiduPCS-Go.Menu.sh (bdgo.sh)-CSDN博客

好像 百度网盘 降速了
总结:
这个脚本可以继续完善,比如文件名加入日期、使用选择菜单(用 bdgo google_drive等),但我现在不想。
因为配置 Postfix 看了太多文档,同时用文章记录配置过程,已经写了 16427 字。现在发现没配置 webmail ,这几个小时一直在配置与修复配置错误中交战,还要在文章中更新,大脑已经宕机前,为了保存工作创建这个脚本。
我上传的脚本是英文注释,不再用 AI 转换注释语言,太麻烦。
运行的输出是用旧的中文注释脚本,这个版本已经被替换。
不知道是不是垃圾腾讯原因,我的 VPS 到 百度云 限速到:

这时在复制同样的文件 /path/to/output/vps-backup.tar.gz 到 google drive.

VPS 控制台输出:

30Mbps TCP/IP 实际应该在 3-4MB 的系统文件传输速度,看来全给 GOOGLE

5GB 文件上传到 GOOGLE DRIVER by google-drive-ocamlfuse 用了 27分13秒 , 平均速率 3.14 MB/s





