Files
shop-platform/scripts/backup_restore/backup_server.sh

278 lines
8.6 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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
# 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 "$@"