#!/bin/bash # ============================================================ # 数据库备份脚本 # # 功能: # - PostgreSQL 全量备份 (pg_dump) # - 备份保留策略: 每日(7天) + 每周(4周) + 每月(12月) # - 压缩 + 可选加密 # - 上传到 MinIO/S3 或本地归档 # - 企业微信通知备份结果 # # Cron 配置 (每天凌晨 2 点执行): # 0 2 * * * /opt/water-management/deploy/production/backup/backup-db.sh >> /var/log/wm-backup.log 2>&1 # ============================================================ set -euo pipefail # ==================== 配置 ==================== # 数据库配置 DB_HOST="${DB_HOST:-localhost}" DB_PORT="${DB_PORT:-5432}" DB_NAME="${POSTGRES_DB:-water_management}" DB_USER="${POSTGRES_USER:-water}" DB_PASSWORD="${POSTGRES_PASSWORD:-}" PGPASSWORD="${DB_PASSWORD}" export PGPASSWORD # 备份目录 BACKUP_BASE="${BACKUP_DIR:-/opt/water-management/backups}" BACKUP_DAILY="${BACKUP_BASE}/daily" BACKUP_WEEKLY="${BACKUP_BASE}/weekly" BACKUP_MONTHLY="${BACKUP_BASE}/monthly" # 保留策略 DAILY_RETENTION=7 WEEKLY_RETENTION=4 MONTHLY_RETENTION=12 # MinIO/S3 配置(可选) S3_ENABLED="${S3_ENABLED:-false}" S3_BUCKET="${S3_BUCKET:-water-backups}" S3_ENDPOINT="${S3_ENDPOINT:-http://localhost:9000}" S3_ACCESS_KEY="${S3_ACCESS_KEY:-}" S3_SECRET_KEY="${S3_SECRET_KEY:-}" # 加密配置(可选) ENCRYPT_ENABLED="${ENCRYPT_ENABLED:-false}" ENCRYPT_PASSPHRASE="${ENCRYPT_PASSPHRASE:-}" # 企业微信 Webhook WECOM_WEBHOOK="${WECOM_WEBHOOK:-}" # ==================== 初始化 ==================== TIMESTAMP=$(date +%Y%m%d_%H%M%S) DATE=$(date +%Y-%m-%d) DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday DAY_OF_MONTH=$(date +%d) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } # 创建备份目录 mkdir -p "$BACKUP_DAILY" "$BACKUP_WEEKLY" "$BACKUP_MONTHLY" # ==================== 备份函数 ==================== # 全量备份 do_full_backup() { local target_dir="$1" local backup_name="wm_${DB_NAME}_${TIMESTAMP}.sql.gz" local backup_path="${target_dir}/${backup_name}" log "📦 开始全量备份: ${DB_NAME}" # pg_dump 全量备份 + gzip 压缩 pg_dump \ -h "$DB_HOST" \ -p "$DB_PORT" \ -U "$DB_USER" \ -d "$DB_NAME" \ --format=plain \ --verbose \ --no-owner \ --no-privileges \ --clean \ --if-exists \ 2>/dev/null | gzip -9 > "$backup_path" local size size=$(du -sh "$backup_path" | cut -f1) log "✅ 备份完成: ${backup_name} (${size})" # 可选加密 if [ "$ENCRYPT_ENABLED" = "true" ] && [ -n "$ENCRYPT_PASSPHRASE" ]; then log "🔐 加密备份文件..." openssl enc -aes-256-cbc -salt -pbkdf2 \ -in "$backup_path" \ -out "${backup_path}.enc" \ -pass "pass:${ENCRYPT_PASSPHRASE}" rm "$backup_path" backup_path="${backup_path}.enc" log "✅ 加密完成" fi # 上传到 S3/MinIO if [ "$S3_ENABLED" = "true" ]; then upload_to_s3 "$backup_path" fi echo "$backup_path" } # 上传到 S3/MinIO upload_to_s3() { local file="$1" local filename filename=$(basename "$file") local s3_path="s3://${S3_BUCKET}/db/${DATE}/${filename}" log "☁️ 上传到 S3: ${s3_path}" # 使用 mc (MinIO Client) 或 aws cli if command -v mc &>/dev/null; then mc alias set wm-s3 "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY" 2>/dev/null mc cp "$file" "wm-s3/${S3_BUCKET}/db/${DATE}/${filename}" 2>/dev/null elif command -v aws &>/dev/null; then aws --endpoint-url "$S3_ENDPOINT" s3 cp "$file" "$s3_path" 2>/dev/null else log "⚠️ 未找到 mc 或 aws 命令,跳过 S3 上传" return 0 fi log "✅ S3 上传完成" } # ==================== 保留策略 ==================== # 清理过期备份 cleanup_old_backups() { log "🧹 执行备份保留策略..." # 每日备份:保留最近 N 天 local daily_count daily_count=$(ls -1 "$BACKUP_DAILY"/*.sql.gz 2>/dev/null | wc -l || echo 0) if [ "$daily_count" -gt "$DAILY_RETENTION" ]; then local to_delete=$((daily_count - DAILY_RETENTION)) log " 清理每日备份: 删除最旧的 ${to_delete} 个文件" ls -1t "$BACKUP_DAILY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f fi # 每周备份:保留最近 N 周 local weekly_count weekly_count=$(ls -1 "$BACKUP_WEEKLY"/*.sql.gz 2>/dev/null | wc -l || echo 0) if [ "$weekly_count" -gt "$WEEKLY_RETENTION" ]; then local to_delete=$((weekly_count - WEEKLY_RETENTION)) log " 清理每周备份: 删除最旧的 ${to_delete} 个文件" ls -1t "$BACKUP_WEEKLY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f fi # 每月备份:保留最近 N 个月 local monthly_count monthly_count=$(ls -1 "$BACKUP_MONTHLY"/*.sql.gz 2>/dev/null | wc -l || echo 0) if [ "$monthly_count" -gt "$MONTHLY_RETENTION" ]; then local to_delete=$((monthly_count - MONTHLY_RETENTION)) log " 清理每月备份: 删除最旧的 ${to_delete} 个文件" ls -1t "$BACKUP_MONTHLY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f fi log "✅ 保留策略执行完毕" } # ==================== 通知 ==================== send_notification() { local status="$1" local message="$2" local backup_path="$3" if [ -z "$WECOM_WEBHOOK" ]; then log "⚠️ 未配置企业微信 Webhook,跳过通知" return 0 fi local emoji="✅" local color="info" if [ "$status" = "failure" ]; then emoji="❌" color="warning" fi local backup_size="N/A" if [ -f "$backup_path" ]; then backup_size=$(du -sh "$backup_path" | cut -f1) fi local payload payload=$(cat <${status}\n**大小:** ${backup_size}\n**时间:** $(date '+%Y-%m-%d %H:%M:%S')\n\n${message}" } } EOF ) curl -s -X POST "$WECOM_WEBHOOK" \ -H "Content-Type: application/json" \ -d "$payload" > /dev/null 2>&1 || true log "📤 通知已发送" } # ==================== 主逻辑 ==================== log "=========================================" log "数据库备份开始 - ${DB_NAME}" log "=========================================" BACKUP_PATH="" STATUS="success" MESSAGE="" # 执行每日备份 log "📅 执行每日备份..." BACKUP_PATH=$(do_full_backup "$BACKUP_DAILY") # 周日执行每周备份 if [ "$DAY_OF_WEEK" = "7" ]; then log "📅 今天是周日,执行每周备份..." do_full_backup "$BACKUP_WEEKLY" > /dev/null fi # 每月1号执行每月备份 if [ "$DAY_OF_MONTH" = "01" ]; then log "📅 今天是月初,执行每月备份..." do_full_backup "$BACKUP_MONTHLY" > /dev/null fi # 清理过期备份 cleanup_old_backups # 发送通知 send_notification "$STATUS" "每日全量备份完成" "$BACKUP_PATH" log "=========================================" log "备份全部完成" log "========================================="