| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- #!/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 <<EOF
- {
- "msgtype": "markdown",
- "markdown": {
- "content": "## ${emoji} 数据库备份通知\n\n**数据库:** ${DB_NAME}\n**状态:** <font color=\"${color}\">${status}</font>\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 "========================================="
|