智慧水务管理系统 - 精河县供水工程综合管理平台

backup-db.sh 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/bin/bash
  2. # ============================================================
  3. # 数据库备份脚本
  4. #
  5. # 功能:
  6. # - PostgreSQL 全量备份 (pg_dump)
  7. # - 备份保留策略: 每日(7天) + 每周(4周) + 每月(12月)
  8. # - 压缩 + 可选加密
  9. # - 上传到 MinIO/S3 或本地归档
  10. # - 企业微信通知备份结果
  11. #
  12. # Cron 配置 (每天凌晨 2 点执行):
  13. # 0 2 * * * /opt/water-management/deploy/production/backup/backup-db.sh >> /var/log/wm-backup.log 2>&1
  14. # ============================================================
  15. set -euo pipefail
  16. # ==================== 配置 ====================
  17. # 数据库配置
  18. DB_HOST="${DB_HOST:-localhost}"
  19. DB_PORT="${DB_PORT:-5432}"
  20. DB_NAME="${POSTGRES_DB:-water_management}"
  21. DB_USER="${POSTGRES_USER:-water}"
  22. DB_PASSWORD="${POSTGRES_PASSWORD:-}"
  23. PGPASSWORD="${DB_PASSWORD}"
  24. export PGPASSWORD
  25. # 备份目录
  26. BACKUP_BASE="${BACKUP_DIR:-/opt/water-management/backups}"
  27. BACKUP_DAILY="${BACKUP_BASE}/daily"
  28. BACKUP_WEEKLY="${BACKUP_BASE}/weekly"
  29. BACKUP_MONTHLY="${BACKUP_BASE}/monthly"
  30. # 保留策略
  31. DAILY_RETENTION=7
  32. WEEKLY_RETENTION=4
  33. MONTHLY_RETENTION=12
  34. # MinIO/S3 配置(可选)
  35. S3_ENABLED="${S3_ENABLED:-false}"
  36. S3_BUCKET="${S3_BUCKET:-water-backups}"
  37. S3_ENDPOINT="${S3_ENDPOINT:-http://localhost:9000}"
  38. S3_ACCESS_KEY="${S3_ACCESS_KEY:-}"
  39. S3_SECRET_KEY="${S3_SECRET_KEY:-}"
  40. # 加密配置(可选)
  41. ENCRYPT_ENABLED="${ENCRYPT_ENABLED:-false}"
  42. ENCRYPT_PASSPHRASE="${ENCRYPT_PASSPHRASE:-}"
  43. # 企业微信 Webhook
  44. WECOM_WEBHOOK="${WECOM_WEBHOOK:-}"
  45. # ==================== 初始化 ====================
  46. TIMESTAMP=$(date +%Y%m%d_%H%M%S)
  47. DATE=$(date +%Y-%m-%d)
  48. DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday
  49. DAY_OF_MONTH=$(date +%d)
  50. log() {
  51. echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
  52. }
  53. # 创建备份目录
  54. mkdir -p "$BACKUP_DAILY" "$BACKUP_WEEKLY" "$BACKUP_MONTHLY"
  55. # ==================== 备份函数 ====================
  56. # 全量备份
  57. do_full_backup() {
  58. local target_dir="$1"
  59. local backup_name="wm_${DB_NAME}_${TIMESTAMP}.sql.gz"
  60. local backup_path="${target_dir}/${backup_name}"
  61. log "📦 开始全量备份: ${DB_NAME}"
  62. # pg_dump 全量备份 + gzip 压缩
  63. pg_dump \
  64. -h "$DB_HOST" \
  65. -p "$DB_PORT" \
  66. -U "$DB_USER" \
  67. -d "$DB_NAME" \
  68. --format=plain \
  69. --verbose \
  70. --no-owner \
  71. --no-privileges \
  72. --clean \
  73. --if-exists \
  74. 2>/dev/null | gzip -9 > "$backup_path"
  75. local size
  76. size=$(du -sh "$backup_path" | cut -f1)
  77. log "✅ 备份完成: ${backup_name} (${size})"
  78. # 可选加密
  79. if [ "$ENCRYPT_ENABLED" = "true" ] && [ -n "$ENCRYPT_PASSPHRASE" ]; then
  80. log "🔐 加密备份文件..."
  81. openssl enc -aes-256-cbc -salt -pbkdf2 \
  82. -in "$backup_path" \
  83. -out "${backup_path}.enc" \
  84. -pass "pass:${ENCRYPT_PASSPHRASE}"
  85. rm "$backup_path"
  86. backup_path="${backup_path}.enc"
  87. log "✅ 加密完成"
  88. fi
  89. # 上传到 S3/MinIO
  90. if [ "$S3_ENABLED" = "true" ]; then
  91. upload_to_s3 "$backup_path"
  92. fi
  93. echo "$backup_path"
  94. }
  95. # 上传到 S3/MinIO
  96. upload_to_s3() {
  97. local file="$1"
  98. local filename
  99. filename=$(basename "$file")
  100. local s3_path="s3://${S3_BUCKET}/db/${DATE}/${filename}"
  101. log "☁️ 上传到 S3: ${s3_path}"
  102. # 使用 mc (MinIO Client) 或 aws cli
  103. if command -v mc &>/dev/null; then
  104. mc alias set wm-s3 "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY" 2>/dev/null
  105. mc cp "$file" "wm-s3/${S3_BUCKET}/db/${DATE}/${filename}" 2>/dev/null
  106. elif command -v aws &>/dev/null; then
  107. aws --endpoint-url "$S3_ENDPOINT" s3 cp "$file" "$s3_path" 2>/dev/null
  108. else
  109. log "⚠️ 未找到 mc 或 aws 命令,跳过 S3 上传"
  110. return 0
  111. fi
  112. log "✅ S3 上传完成"
  113. }
  114. # ==================== 保留策略 ====================
  115. # 清理过期备份
  116. cleanup_old_backups() {
  117. log "🧹 执行备份保留策略..."
  118. # 每日备份:保留最近 N 天
  119. local daily_count
  120. daily_count=$(ls -1 "$BACKUP_DAILY"/*.sql.gz 2>/dev/null | wc -l || echo 0)
  121. if [ "$daily_count" -gt "$DAILY_RETENTION" ]; then
  122. local to_delete=$((daily_count - DAILY_RETENTION))
  123. log " 清理每日备份: 删除最旧的 ${to_delete} 个文件"
  124. ls -1t "$BACKUP_DAILY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f
  125. fi
  126. # 每周备份:保留最近 N 周
  127. local weekly_count
  128. weekly_count=$(ls -1 "$BACKUP_WEEKLY"/*.sql.gz 2>/dev/null | wc -l || echo 0)
  129. if [ "$weekly_count" -gt "$WEEKLY_RETENTION" ]; then
  130. local to_delete=$((weekly_count - WEEKLY_RETENTION))
  131. log " 清理每周备份: 删除最旧的 ${to_delete} 个文件"
  132. ls -1t "$BACKUP_WEEKLY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f
  133. fi
  134. # 每月备份:保留最近 N 个月
  135. local monthly_count
  136. monthly_count=$(ls -1 "$BACKUP_MONTHLY"/*.sql.gz 2>/dev/null | wc -l || echo 0)
  137. if [ "$monthly_count" -gt "$MONTHLY_RETENTION" ]; then
  138. local to_delete=$((monthly_count - MONTHLY_RETENTION))
  139. log " 清理每月备份: 删除最旧的 ${to_delete} 个文件"
  140. ls -1t "$BACKUP_MONTHLY"/*.sql.gz 2>/dev/null | tail -n "$to_delete" | xargs rm -f
  141. fi
  142. log "✅ 保留策略执行完毕"
  143. }
  144. # ==================== 通知 ====================
  145. send_notification() {
  146. local status="$1"
  147. local message="$2"
  148. local backup_path="$3"
  149. if [ -z "$WECOM_WEBHOOK" ]; then
  150. log "⚠️ 未配置企业微信 Webhook,跳过通知"
  151. return 0
  152. fi
  153. local emoji="✅"
  154. local color="info"
  155. if [ "$status" = "failure" ]; then
  156. emoji="❌"
  157. color="warning"
  158. fi
  159. local backup_size="N/A"
  160. if [ -f "$backup_path" ]; then
  161. backup_size=$(du -sh "$backup_path" | cut -f1)
  162. fi
  163. local payload
  164. payload=$(cat <<EOF
  165. {
  166. "msgtype": "markdown",
  167. "markdown": {
  168. "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}"
  169. }
  170. }
  171. EOF
  172. )
  173. curl -s -X POST "$WECOM_WEBHOOK" \
  174. -H "Content-Type: application/json" \
  175. -d "$payload" > /dev/null 2>&1 || true
  176. log "📤 通知已发送"
  177. }
  178. # ==================== 主逻辑 ====================
  179. log "========================================="
  180. log "数据库备份开始 - ${DB_NAME}"
  181. log "========================================="
  182. BACKUP_PATH=""
  183. STATUS="success"
  184. MESSAGE=""
  185. # 执行每日备份
  186. log "📅 执行每日备份..."
  187. BACKUP_PATH=$(do_full_backup "$BACKUP_DAILY")
  188. # 周日执行每周备份
  189. if [ "$DAY_OF_WEEK" = "7" ]; then
  190. log "📅 今天是周日,执行每周备份..."
  191. do_full_backup "$BACKUP_WEEKLY" > /dev/null
  192. fi
  193. # 每月1号执行每月备份
  194. if [ "$DAY_OF_MONTH" = "01" ]; then
  195. log "📅 今天是月初,执行每月备份..."
  196. do_full_backup "$BACKUP_MONTHLY" > /dev/null
  197. fi
  198. # 清理过期备份
  199. cleanup_old_backups
  200. # 发送通知
  201. send_notification "$STATUS" "每日全量备份完成" "$BACKUP_PATH"
  202. log "========================================="
  203. log "备份全部完成"
  204. log "========================================="