chore: 针对备份及还原处理
This commit is contained in:
21
scripts/backup_restore/.env
Normal file
21
scripts/backup_restore/.env
Normal file
@@ -0,0 +1,21 @@
|
||||
# 备份配置
|
||||
BACKUP_NAME="server-production"
|
||||
BACKUP_SOURCE="/var/www/html /etc/nginx"
|
||||
BACKUP_DEST="/backups"
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST="localhost"
|
||||
DB_USER="backup_user"
|
||||
DB_PASSWORD="secure_password"
|
||||
|
||||
# 通知配置
|
||||
SLACK_WEBHOOK="https://hooks.slack.com/services/..."
|
||||
EMAIL_TO="admin@company.com"
|
||||
|
||||
# 加密配置
|
||||
ENCRYPTION_KEY_PATH="/etc/backup/keys"
|
||||
GPG_PASSPHRASE="your_secure_passphrase"
|
||||
|
||||
# 长路径支持
|
||||
LONG_PATH_SUPPORT="true"
|
||||
TAR_OPTIONS="--force-local"
|
||||
59
scripts/backup_restore/backup_config.json
Normal file
59
scripts/backup_restore/backup_config.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"description": "服务器源码备份还原配置",
|
||||
"backup": {
|
||||
"source_dirs": [
|
||||
"/var/www/html",
|
||||
"/etc/nginx",
|
||||
"/etc/mysql",
|
||||
"/home/user/projects"
|
||||
],
|
||||
"exclude_patterns": [
|
||||
"*.log",
|
||||
"*.tmp",
|
||||
"node_modules",
|
||||
".git",
|
||||
"__pycache__",
|
||||
"*.pyc",
|
||||
".DS_Store",
|
||||
"thumbs.db"
|
||||
],
|
||||
"max_backup_size": "10G",
|
||||
"backup_retention_days": 30,
|
||||
"long_path_support": true
|
||||
},
|
||||
"storage": {
|
||||
"local_path": "/backups/server-src",
|
||||
"remote_path": "user@backup-server:/backups",
|
||||
"cloud_storage": {
|
||||
"s3_bucket": "my-backup-bucket",
|
||||
"azure_container": "backups",
|
||||
"gcs_bucket": "server-backups"
|
||||
}
|
||||
},
|
||||
"database": {
|
||||
"enabled": true,
|
||||
"mysql": {
|
||||
"host": "localhost",
|
||||
"user": "backup_user",
|
||||
"password": "secure_password",
|
||||
"databases": ["app_db", "user_db"]
|
||||
},
|
||||
"postgresql": {
|
||||
"host": "localhost",
|
||||
"user": "backup_user",
|
||||
"password": "secure_password",
|
||||
"databases": ["app_db"]
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"email": "admin@company.com",
|
||||
"slack_webhook": "https://hooks.slack.com/services/...",
|
||||
"discord_webhook": "https://discord.com/api/webhooks/..."
|
||||
},
|
||||
"encryption": {
|
||||
"enabled": true,
|
||||
"public_key": "/path/to/public.key",
|
||||
"private_key": "/path/to/private.key"
|
||||
}
|
||||
}
|
||||
75
scripts/backup_restore/backup_config.php
Normal file
75
scripts/backup_restore/backup_config.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 备份还原工具默认配置文件
|
||||
* 可通过--config参数指定此文件路径使用
|
||||
*/
|
||||
return [
|
||||
// 备份文件保存目录
|
||||
'backup_dir' => '/var/backups',
|
||||
|
||||
// 排除规则 - 使用正则表达式匹配需要排除的文件路径
|
||||
'exclude_patterns' => [
|
||||
'/\.git/', // 排除git版本控制目录
|
||||
'/node_modules/', // 排除npm依赖
|
||||
'/\.log$/', // 排除日志文件
|
||||
'/\.tmp$/', // 排除临时文件
|
||||
'/cache/', // 排除缓存目录
|
||||
'/temp/', // 排除临时目录
|
||||
'/tmp/', // 排除临时目录
|
||||
'/logs/', // 排除日志目录
|
||||
'/runtime/', // 排除运行时目录
|
||||
'/uploads/', // 排除上传目录
|
||||
'/attachment/', // 排除附件目录
|
||||
],
|
||||
|
||||
// 包含规则 - 使用正则表达式匹配需要强制包含的文件路径
|
||||
// 即使这些文件被排除规则匹配,也会被包含
|
||||
'include_patterns' => [
|
||||
'/.well-known/', // 包含well-known目录
|
||||
'/addon/', // 包含插件目录
|
||||
'/addons/', // 包含插件目录
|
||||
'/app/', // 包含应用目录
|
||||
'/config/', // 包含配置目录
|
||||
'/extend/', // 包含扩展目录
|
||||
// '/h5/', // 包含h5目录
|
||||
// '/hwapp/', // 包含hwapp目录
|
||||
'/public/', // 包含公共目录
|
||||
'/vendor/', // 包含composer依赖
|
||||
// '/web/', // 包含web目录
|
||||
|
||||
// 以下是包含的文件
|
||||
'/.404.html', // 包含404页面
|
||||
'/index.php', // 包含入口文件
|
||||
'/install.php', // 包含安装文件
|
||||
'/install.lock', // 包含安装锁文件
|
||||
|
||||
'/.env', // 包含环境变量文件
|
||||
'/.env.test', // 包含测试环境变量文件
|
||||
'/.env.production', // 包含生产环境变量文件
|
||||
'/.env.staging', // 包含预发布环境变量文件
|
||||
'/.env.development', // 包含开发环境变量文件
|
||||
'/.env.local', // 包含本地环境变量文件
|
||||
'/.gitignore', // 包含git忽略文件
|
||||
'/.htaccess', // 包含htaccess文件
|
||||
'/.user.ini', // 包含user.ini文件
|
||||
'/composer.json', // 包含composer.json文件
|
||||
'/composer.lock', // 包含composer.lock文件
|
||||
],
|
||||
|
||||
// 备份模式 - 使用通配符匹配文件类型
|
||||
// 这里使用了更全面的文件类型覆盖,确保支持所有常见文件
|
||||
'backup_patterns' => [
|
||||
// 如果为空数组,将备份所有文件(不建议)
|
||||
],
|
||||
|
||||
// 保留的最大备份文件数量,超过此数量会自动删除最旧的备份
|
||||
'max_backups' => 30,
|
||||
|
||||
// 压缩级别(1-9,1最快但压缩率最低,9最慢但压缩率最高)
|
||||
'compression_level' => 6,
|
||||
|
||||
// 是否保留文件权限
|
||||
'preserve_permissions' => true
|
||||
|
||||
];
|
||||
278
scripts/backup_restore/backup_server.sh
Normal file
278
scripts/backup_restore/backup_server.sh
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/bin/bash
|
||||
# backup_server.sh - 支持长路径和配置文件的智能备份脚本
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 脚本配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/backup_config.json"
|
||||
ENV_FILE="${SCRIPT_DIR}/.env"
|
||||
LOG_FILE="${SCRIPT_DIR}/backup_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"; }
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE"; }
|
||||
|
||||
# 加载配置
|
||||
load_config() {
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
error "配置文件不存在: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
source "$ENV_FILE"
|
||||
log "环境配置文件已加载"
|
||||
fi
|
||||
|
||||
# 解析JSON配置
|
||||
BACKUP_NAME=$(jq -r '.backup.name // "server-backup"' "$CONFIG_FILE")
|
||||
SOURCE_DIRS=$(jq -r '.backup.source_dirs[]?' "$CONFIG_FILE" | tr '\n' ' ')
|
||||
EXCLUDE_PATTERNS=$(jq -r '.backup.exclude_patterns[]?' "$CONFIG_FILE")
|
||||
LONG_PATH_SUPPORT=$(jq -r '.backup.long_path_support // "true"' "$CONFIG_FILE")
|
||||
LOCAL_BACKUP_PATH=$(jq -r '.storage.local_path // "/backups"' "$CONFIG_FILE")
|
||||
|
||||
log "配置文件加载完成"
|
||||
}
|
||||
|
||||
# 检查长路径支持
|
||||
check_long_path_support() {
|
||||
if [[ "$LONG_PATH_SUPPORT" == "true" ]]; then
|
||||
# 检查tar版本是否支持长路径
|
||||
TAR_VERSION=$(tar --version | head -1 | grep -oE '[0-9]+\.[0-9]+')
|
||||
if [[ $(echo "$TAR_VERSION >= 1.22" | bc -l 2>/dev/null) -eq 1 ]]; then
|
||||
TAR_OPTIONS="--force-local"
|
||||
log "tar版本 $TAR_VERSION 支持长路径"
|
||||
else
|
||||
warn "tar版本较低,长路径支持可能受限"
|
||||
TAR_OPTIONS=""
|
||||
fi
|
||||
else
|
||||
TAR_OPTIONS=""
|
||||
info "长路径支持已禁用"
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建排除文件
|
||||
create_exclude_file() {
|
||||
local exclude_file="${SCRIPT_DIR}/exclude_patterns.txt"
|
||||
|
||||
# 从配置文件读取排除模式
|
||||
jq -r '.backup.exclude_patterns[]?' "$CONFIG_FILE" > "$exclude_file"
|
||||
|
||||
# 添加系统默认排除
|
||||
cat >> "$exclude_file" << EOF
|
||||
*.log
|
||||
*.tmp
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
._*
|
||||
EOF
|
||||
|
||||
echo "$exclude_file"
|
||||
}
|
||||
|
||||
# 备份数据库
|
||||
backup_databases() {
|
||||
local db_enabled=$(jq -r '.database.enabled // "false"' "$CONFIG_FILE")
|
||||
|
||||
if [[ "$db_enabled" != "true" ]]; then
|
||||
info "数据库备份已禁用"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local db_backup_dir="${LOCAL_BACKUP_PATH}/database_${timestamp}"
|
||||
|
||||
mkdir -p "$db_backup_dir"
|
||||
log "开始备份数据库..."
|
||||
|
||||
# MySQL备份
|
||||
if command -v mysqldump > /dev/null; then
|
||||
local mysql_config=$(jq -r '.database.mysql // {}' "$CONFIG_FILE")
|
||||
if [[ "$mysql_config" != "{}" ]]; then
|
||||
local mysql_host=$(jq -r '.host // "localhost"' <<< "$mysql_config")
|
||||
local mysql_user=$(jq -r '.user // "root"' <<< "$mysql_config")
|
||||
local mysql_password=$(jq -r '.password // ""' <<< "$mysql_config")
|
||||
local databases=$(jq -r '.databases[]?' <<< "$mysql_config")
|
||||
|
||||
for db in $databases; do
|
||||
info "备份MySQL数据库: $db"
|
||||
if [[ -n "$mysql_password" ]]; then
|
||||
mysqldump -h "$mysql_host" -u "$mysql_user" -p"$mysql_password" "$db" > "${db_backup_dir}/${db}_mysql.sql"
|
||||
else
|
||||
mysqldump -h "$mysql_host" -u "$mysql_user" "$db" > "${db_backup_dir}/${db}_mysql.sql"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# PostgreSQL备份
|
||||
if command -v pg_dump > /dev/null; then
|
||||
local pgsql_config=$(jq -r '.database.postgresql // {}' "$CONFIG_FILE")
|
||||
if [[ "$pgsql_config" != "{}" ]]; then
|
||||
local pgsql_host=$(jq -r '.host // "localhost"' <<< "$pgsql_config")
|
||||
local pgsql_user=$(jq -r '.user // "postgres"' <<< "$pgsql_config")
|
||||
local databases=$(jq -r '.databases[]?' <<< "$pgsql_config")
|
||||
|
||||
for db in $databases; do
|
||||
info "备份PostgreSQL数据库: $db"
|
||||
pg_dump -h "$pgsql_host" -U "$pgsql_user" "$db" > "${db_backup_dir}/${db}_pgsql.sql"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
log "数据库备份完成: $db_backup_dir"
|
||||
}
|
||||
|
||||
# 主备份函数
|
||||
perform_backup() {
|
||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="${LOCAL_BACKUP_PATH}/backup_${timestamp}.tar.gz"
|
||||
local exclude_file=$(create_exclude_file)
|
||||
|
||||
log "开始备份服务器源码..."
|
||||
info "源目录: $SOURCE_DIRS"
|
||||
info "备份文件: $backup_file"
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p "$LOCAL_BACKUP_PATH"
|
||||
|
||||
# 备份数据库
|
||||
backup_databases
|
||||
|
||||
# 使用tar进行备份(支持长路径)
|
||||
local tar_command="tar $TAR_OPTIONS -czf \"$backup_file\" --exclude-from=\"$exclude_file\" $SOURCE_DIRS"
|
||||
|
||||
log "执行备份命令: $tar_command"
|
||||
|
||||
if eval "$tar_command"; then
|
||||
local backup_size=$(du -h "$backup_file" | cut -f1)
|
||||
log "✅ 备份成功! 文件大小: $backup_size"
|
||||
echo "$backup_file" > "${SCRIPT_DIR}/latest_backup.txt"
|
||||
else
|
||||
error "❌ 备份失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -f "$exclude_file"
|
||||
}
|
||||
|
||||
# 加密备份文件
|
||||
encrypt_backup() {
|
||||
local encryption_enabled=$(jq -r '.encryption.enabled // "false"' "$CONFIG_FILE")
|
||||
|
||||
if [[ "$encryption_enabled" != "true" ]]; then
|
||||
info "加密功能已禁用"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local backup_file=$(cat "${SCRIPT_DIR}/latest_backup.txt" 2>/dev/null || echo "")
|
||||
if [[ -z "$backup_file" || ! -f "$backup_file" ]]; then
|
||||
warn "未找到备份文件进行加密"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local public_key=$(jq -r '.encryption.public_key // ""' "$CONFIG_FILE")
|
||||
if [[ -z "$public_key" || ! -f "$public_key" ]]; then
|
||||
error "加密公钥不存在: $public_key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "开始加密备份文件..."
|
||||
|
||||
if command -v gpg > /dev/null; then
|
||||
gpg --encrypt --recipient-file "$public_key" --trust-model always "$backup_file"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
local encrypted_file="${backup_file}.gpg"
|
||||
log "✅ 加密完成: $encrypted_file"
|
||||
# 可选:删除未加密文件
|
||||
# rm -f "$backup_file"
|
||||
else
|
||||
error "❌ 加密失败"
|
||||
fi
|
||||
else
|
||||
error "GPG未安装,无法加密"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理旧备份
|
||||
cleanup_old_backups() {
|
||||
local retention_days=$(jq -r '.backup.backup_retention_days // 30' "$CONFIG_FILE")
|
||||
|
||||
log "清理超过 $retention_days 天的旧备份..."
|
||||
|
||||
find "$LOCAL_BACKUP_PATH" -name "backup_*.tar.gz" -mtime "+$retention_days" -delete
|
||||
find "$LOCAL_BACKUP_PATH" -name "backup_*.tar.gz.gpg" -mtime "+$retention_days" -delete
|
||||
find "$LOCAL_BACKUP_PATH" -name "database_*" -type d -mtime "+$retention_days" -exec rm -rf {} +
|
||||
|
||||
log "旧备份清理完成"
|
||||
}
|
||||
|
||||
# 发送通知
|
||||
send_notification() {
|
||||
local backup_file=$(cat "${SCRIPT_DIR}/latest_backup.txt" 2>/dev/null || echo "未知")
|
||||
local backup_size=$(du -h "$backup_file" 2>/dev/null | cut -f1 || echo "未知")
|
||||
|
||||
local message="✅ 服务器备份完成\n时间: $(date)\n文件: $(basename "$backup_file")\n大小: $backup_size"
|
||||
|
||||
# Slack通知
|
||||
local slack_webhook=$(jq -r '.notifications.slack_webhook // ""' "$CONFIG_FILE")
|
||||
if [[ -n "$slack_webhook" ]]; then
|
||||
curl -X POST -H 'Content-type: application/json' \
|
||||
--data "{\"text\":\"$message\"}" "$slack_webhook" > /dev/null 2>&1 &&
|
||||
log "Slack通知已发送"
|
||||
fi
|
||||
|
||||
# 邮件通知(需要配置邮件服务器)
|
||||
local email_to=$(jq -r '.notifications.email // ""' "$CONFIG_FILE")
|
||||
if [[ -n "$email_to" && -f "/usr/sbin/sendmail" ]]; then
|
||||
echo -e "Subject: 服务器备份完成\n\n$message" | sendmail "$email_to"
|
||||
log "邮件通知已发送"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log "🚀 启动服务器源码备份流程"
|
||||
log "========================================"
|
||||
|
||||
# 加载配置
|
||||
load_config
|
||||
|
||||
# 检查长路径支持
|
||||
check_long_path_support
|
||||
|
||||
# 执行备份
|
||||
perform_backup
|
||||
|
||||
# 加密备份
|
||||
encrypt_backup
|
||||
|
||||
# 清理旧备份
|
||||
cleanup_old_backups
|
||||
|
||||
# 发送通知
|
||||
send_notification
|
||||
|
||||
log "🎉 备份流程完成!"
|
||||
log "========================================"
|
||||
}
|
||||
|
||||
# 异常处理
|
||||
trap 'error "脚本执行中断"; exit 1' INT TERM
|
||||
trap 'cleanup_old_backups' EXIT
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
111
scripts/backup_restore/install_backup_system.sh
Normal file
111
scripts/backup_restore/install_backup_system.sh
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# install_backup_system.sh - 安装备份系统
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
|
||||
|
||||
install_dependencies() {
|
||||
log "安装系统依赖..."
|
||||
|
||||
# 检测系统类型
|
||||
if command -v apt-get > /dev/null; then
|
||||
# Debian/Ubuntu
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tar gzip jq gpg mysql-client postgresql-client curl
|
||||
elif command -v yum > /dev/null; then
|
||||
# CentOS/RHEL
|
||||
sudo yum install -y tar gzip jq gnupg mysql postgresql curl
|
||||
elif command -v dnf > /dev/null; then
|
||||
# Fedora
|
||||
sudo dnf install -y tar gzip jq gnupg mysql postgresql curl
|
||||
else
|
||||
log "⚠️ 无法自动安装依赖,请手动安装: tar, gzip, jq, gpg, mysql-client, postgresql-client, curl"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_directories() {
|
||||
log "创建目录结构..."
|
||||
|
||||
local backup_dir=$(jq -r '.storage.local_path // "/opt/backups"' "${SCRIPT_DIR}/backup_config.json")
|
||||
|
||||
sudo mkdir -p "$backup_dir"
|
||||
sudo chmod 755 "$backup_dir"
|
||||
sudo chown "$(whoami):$(whoami)" "$backup_dir" 2>/dev/null || true
|
||||
|
||||
log "备份目录: $backup_dir"
|
||||
}
|
||||
|
||||
setup_crontab() {
|
||||
log "设置定时任务..."
|
||||
|
||||
local schedule="0 2 * * *" # 每天凌晨2点
|
||||
|
||||
# 检查是否已存在定时任务
|
||||
if crontab -l 2>/dev/null | grep -q "backup_server.sh"; then
|
||||
log "⚠️ 定时任务已存在,跳过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 添加定时任务
|
||||
(crontab -l 2>/dev/null; echo "$schedule ${SCRIPT_DIR}/backup_server.sh") | crontab -
|
||||
|
||||
log "✅ 定时任务已添加: $schedule"
|
||||
}
|
||||
|
||||
generate_ssh_key() {
|
||||
log "生成SSH密钥(用于远程备份)..."
|
||||
|
||||
local ssh_dir="${SCRIPT_DIR}/.ssh"
|
||||
mkdir -p "$ssh_dir"
|
||||
chmod 700 "$ssh_dir"
|
||||
|
||||
if [[ ! -f "${ssh_dir}/id_rsa" ]]; then
|
||||
ssh-keygen -t rsa -b 4096 -f "${ssh_dir}/id_rsa" -N "" -q
|
||||
log "✅ SSH密钥已生成: ${ssh_dir}/id_rsa.pub"
|
||||
else
|
||||
log "⚠️ SSH密钥已存在"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
log "🚀 开始安装备份系统"
|
||||
echo "========================================"
|
||||
|
||||
# 检查配置文件
|
||||
if [[ ! -f "${SCRIPT_DIR}/backup_config.json" ]]; then
|
||||
log "❌ 配置文件 backup_config.json 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装依赖
|
||||
install_dependencies
|
||||
|
||||
# 设置目录
|
||||
setup_directories
|
||||
|
||||
# 设置定时任务
|
||||
setup_crontab
|
||||
|
||||
# 生成SSH密钥
|
||||
generate_ssh_key
|
||||
|
||||
# 设置脚本权限
|
||||
chmod +x "${SCRIPT_DIR}/backup_server.sh"
|
||||
chmod +x "${SCRIPT_DIR}/restore_server.sh"
|
||||
chmod +x "${SCRIPT_DIR}/verify_backup.sh"
|
||||
|
||||
log "🎉 备份系统安装完成!"
|
||||
echo ""
|
||||
echo "📋 下一步操作:"
|
||||
echo " 1. 编辑 backup_config.json 配置备份参数"
|
||||
echo " 2. 编辑 .env 文件设置环境变量"
|
||||
echo " 3. 测试备份: ${SCRIPT_DIR}/backup_server.sh"
|
||||
echo " 4. 查看定时任务: crontab -l"
|
||||
echo ""
|
||||
echo "💡 提示: 确保配置中的目录路径有读写权限"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
10
scripts/backup_restore/readme.md
Normal file
10
scripts/backup_restore/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 备份还原脚本
|
||||
|
||||
|
||||
## 功能
|
||||
|
||||
- 备份服务器源码
|
||||
- 还原服务器源码
|
||||
- 支持长文件路径
|
||||
- 支持根据配置文件备份
|
||||
- 支持根据配置文件还原
|
||||
275
scripts/backup_restore/restore_server.sh
Normal file
275
scripts/backup_restore/restore_server.sh
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/bin/bash
|
||||
# restore_server.sh - 支持长路径和配置文件的还原脚本
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/backup_config.json"
|
||||
RESTORE_LOG="${SCRIPT_DIR}/restore_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$RESTORE_LOG"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$RESTORE_LOG"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$RESTORE_LOG"; }
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$RESTORE_LOG"; }
|
||||
|
||||
# 显示可用备份
|
||||
list_backups() {
|
||||
local backup_dir=$(jq -r '.storage.local_path // "/backups"' "$CONFIG_FILE")
|
||||
|
||||
if [[ ! -d "$backup_dir" ]]; then
|
||||
error "备份目录不存在: $backup_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "可用的备份文件:"
|
||||
find "$backup_dir" -name "backup_*.tar.gz" -o -name "backup_*.tar.gz.gpg" | sort | while read file; do
|
||||
local size=$(du -h "$file" | cut -f1)
|
||||
local date=$(stat -c %y "$file" | cut -d' ' -f1)
|
||||
echo " 📦 $file ($size, $date)"
|
||||
done
|
||||
}
|
||||
|
||||
# 解密备份文件
|
||||
decrypt_backup() {
|
||||
local encrypted_file="$1"
|
||||
local decrypted_file="${encrypted_file%.gpg}"
|
||||
|
||||
local private_key=$(jq -r '.encryption.private_key // ""' "$CONFIG_FILE")
|
||||
if [[ -z "$private_key" || ! -f "$private_key" ]]; then
|
||||
error "解密私钥不存在: $private_key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "解密备份文件: $encrypted_file"
|
||||
|
||||
if command -v gpg > /dev/null; then
|
||||
gpg --decrypt --pinentry-mode loopback --passphrase-file <(echo "$GPG_PASSPHRASE") \
|
||||
--output "$decrypted_file" "$encrypted_file"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
log "✅ 解密完成: $decrypted_file"
|
||||
echo "$decrypted_file"
|
||||
else
|
||||
error "❌ 解密失败"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
error "GPG未安装,无法解密"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证备份文件
|
||||
verify_backup() {
|
||||
local backup_file="$1"
|
||||
|
||||
log "验证备份文件: $backup_file"
|
||||
|
||||
if [[ ! -f "$backup_file" ]]; then
|
||||
error "备份文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查文件完整性
|
||||
if tar -tzf "$backup_file" > /dev/null 2>&1; then
|
||||
log "✅ 备份文件完整性验证通过"
|
||||
return 0
|
||||
else
|
||||
error "❌ 备份文件损坏或格式错误"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 还原数据库
|
||||
restore_databases() {
|
||||
local backup_dir="$1"
|
||||
local timestamp=$(basename "$backup_dir" | sed 's/database_//')
|
||||
|
||||
if [[ ! -d "$backup_dir" ]]; then
|
||||
warn "未找到数据库备份目录: $backup_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "开始还原数据库..."
|
||||
|
||||
# MySQL还原
|
||||
for sql_file in "$backup_dir"/*_mysql.sql; do
|
||||
if [[ -f "$sql_file" ]]; then
|
||||
local db_name=$(basename "$sql_file" | sed 's/_mysql.sql//')
|
||||
info "还原MySQL数据库: $db_name"
|
||||
|
||||
local mysql_config=$(jq -r '.database.mysql // {}' "$CONFIG_FILE")
|
||||
local mysql_host=$(jq -r '.host // "localhost"' <<< "$mysql_config")
|
||||
local mysql_user=$(jq -r '.user // "root"' <<< "$mysql_config")
|
||||
local mysql_password=$(jq -r '.password // ""' <<< "$mysql_config")
|
||||
|
||||
if [[ -n "$mysql_password" ]]; then
|
||||
mysql -h "$mysql_host" -u "$mysql_user" -p"$mysql_password" "$db_name" < "$sql_file"
|
||||
else
|
||||
mysql -h "$mysql_host" -u "$mysql_user" "$db_name" < "$sql_file"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# PostgreSQL还原
|
||||
for sql_file in "$backup_dir"/*_pgsql.sql; do
|
||||
if [[ -f "$sql_file" ]]; then
|
||||
local db_name=$(basename "$sql_file" | sed 's/_pgsql.sql//')
|
||||
info "还原PostgreSQL数据库: $db_name"
|
||||
|
||||
local pgsql_config=$(jq -r '.database.postgresql // {}' "$CONFIG_FILE")
|
||||
local pgsql_host=$(jq -r '.host // "localhost"' <<< "$pgsql_config")
|
||||
local pgsql_user=$(jq -r '.user // "postgres"' <<< "$pgsql_config")
|
||||
|
||||
PGPASSWORD=$(jq -r '.password // ""' <<< "$pgsql_config") \
|
||||
psql -h "$pgsql_host" -U "$pgsql_user" -d "$db_name" -f "$sql_file"
|
||||
fi
|
||||
done
|
||||
|
||||
log "数据库还原完成"
|
||||
}
|
||||
|
||||
# 主还原函数
|
||||
perform_restore() {
|
||||
local backup_file="$1"
|
||||
local restore_path="$2"
|
||||
local decrypt="$3"
|
||||
|
||||
# 解密备份文件(如果需要)
|
||||
if [[ "$decrypt" == "true" && "$backup_file" == *.gpg ]]; then
|
||||
backup_file=$(decrypt_backup "$backup_file")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 验证备份文件
|
||||
verify_backup "$backup_file" || return 1
|
||||
|
||||
# 创建还原目录
|
||||
mkdir -p "$restore_path"
|
||||
|
||||
log "开始还原到: $restore_path"
|
||||
|
||||
# 检查长路径支持
|
||||
local long_path_support=$(jq -r '.backup.long_path_support // "true"' "$CONFIG_FILE")
|
||||
local tar_options=""
|
||||
if [[ "$long_path_support" == "true" ]]; then
|
||||
tar_options="--force-local"
|
||||
fi
|
||||
|
||||
# 执行还原
|
||||
if tar $tar_options -xzf "$backup_file" -C "$restore_path"; then
|
||||
log "✅ 文件还原成功"
|
||||
|
||||
# 还原数据库
|
||||
local backup_dir=$(dirname "$backup_file")
|
||||
local timestamp=$(basename "$backup_file" | sed 's/backup_//' | sed 's/\.tar\.gz\(\.gpg\)*//')
|
||||
local db_backup_dir="${backup_dir}/database_${timestamp}"
|
||||
|
||||
restore_databases "$db_backup_dir"
|
||||
|
||||
return 0
|
||||
else
|
||||
error "❌ 文件还原失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 交互式还原
|
||||
interactive_restore() {
|
||||
log "🔍 交互式还原模式"
|
||||
echo "========================================"
|
||||
|
||||
# 显示可用备份
|
||||
list_backups
|
||||
|
||||
echo ""
|
||||
read -p "📥 请输入要还原的备份文件路径: " backup_file
|
||||
|
||||
if [[ ! -f "$backup_file" ]]; then
|
||||
error "文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 询问还原路径
|
||||
read -p "📁 请输入还原路径 [默认: /tmp/restore]: " restore_path
|
||||
restore_path=${restore_path:-/tmp/restore}
|
||||
|
||||
# 询问是否需要解密
|
||||
local decrypt="false"
|
||||
if [[ "$backup_file" == *.gpg ]]; then
|
||||
read -p "🔐 是否需要解密? (y/N): " need_decrypt
|
||||
if [[ "$need_decrypt" == "y" || "$need_decrypt" == "Y" ]]; then
|
||||
decrypt="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 确认操作
|
||||
echo ""
|
||||
echo "📋 还原配置:"
|
||||
echo " 备份文件: $backup_file"
|
||||
echo " 还原路径: $restore_path"
|
||||
echo " 解密操作: $decrypt"
|
||||
echo ""
|
||||
|
||||
read -p "⚠️ 确认开始还原? (y/N): " confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
log "操作取消"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 执行还原
|
||||
perform_restore "$backup_file" "$restore_path" "$decrypt"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local mode="${1:-interactive}"
|
||||
|
||||
log "🔄 启动服务器源码还原流程"
|
||||
log "========================================"
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
error "配置文件不存在: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$mode" in
|
||||
"interactive")
|
||||
interactive_restore
|
||||
;;
|
||||
"auto")
|
||||
local backup_file="${2:-}"
|
||||
local restore_path="${3:-/tmp/restore}"
|
||||
if [[ -z "$backup_file" ]]; then
|
||||
error "自动模式需要指定备份文件路径"
|
||||
exit 1
|
||||
fi
|
||||
perform_restore "$backup_file" "$restore_path" "false"
|
||||
;;
|
||||
"list")
|
||||
list_backups
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 [interactive|auto|list] [backup_file] [restore_path]"
|
||||
echo " interactive - 交互式还原"
|
||||
echo " auto - 自动还原"
|
||||
echo " list - 列出备份文件"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "🎉 还原流程完成!"
|
||||
log "========================================"
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
82
scripts/backup_restore/verify_backup.sh
Normal file
82
scripts/backup_restore/verify_backup.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
# verify_backup.sh - 验证备份完整性
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/backup_config.json"
|
||||
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
|
||||
|
||||
verify_backup_integrity() {
|
||||
local backup_file="$1"
|
||||
|
||||
log "验证备份完整性: $backup_file"
|
||||
|
||||
# 检查文件是否存在
|
||||
if [[ ! -f "$backup_file" ]]; then
|
||||
echo "❌ 备份文件不存在"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查文件大小
|
||||
local file_size=$(stat -c%s "$backup_file")
|
||||
if [[ $file_size -eq 0 ]]; then
|
||||
echo "❌ 备份文件为空"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "✅ 文件大小: $(numfmt --to=iec-i --suffix=B $file_size)"
|
||||
|
||||
# 检查tar归档完整性
|
||||
if tar -tzf "$backup_file" > /dev/null 2>&1; then
|
||||
echo "✅ 归档完整性验证通过"
|
||||
|
||||
# 统计文件数量
|
||||
local file_count=$(tar -tzf "$backup_file" | wc -l)
|
||||
echo "✅ 包含文件数: $file_count"
|
||||
|
||||
# 检查长路径文件
|
||||
local long_path_files=$(tar -tzf "$backup_file" | grep -c '/.\{100,\}')
|
||||
if [[ $long_path_files -gt 0 ]]; then
|
||||
echo "📁 长路径文件数: $long_path_files"
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
echo "❌ 归档损坏或格式错误"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local backup_file="${1:-}"
|
||||
|
||||
if [[ -z "$backup_file" ]]; then
|
||||
# 使用最新备份
|
||||
local backup_dir=$(jq -r '.storage.local_path // "/backups"' "$CONFIG_FILE")
|
||||
backup_file=$(find "$backup_dir" -name "backup_*.tar.gz" -o -name "backup_*.tar.gz.gpg" | sort | tail -1)
|
||||
|
||||
if [[ -z "$backup_file" ]]; then
|
||||
echo "❌ 未找到备份文件"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "🔍 备份验证报告"
|
||||
echo "========================================"
|
||||
echo "备份文件: $backup_file"
|
||||
echo "验证时间: $(date)"
|
||||
echo "----------------------------------------"
|
||||
|
||||
if verify_backup_integrity "$backup_file"; then
|
||||
echo "✅ 备份验证通过"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ 备份验证失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user