From f98be415173df8f2ee81deca8168bc46a8f427e1 Mon Sep 17 00:00:00 2001 From: OG T Date: Thu, 9 Apr 2026 09:16:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(ops):=20pg-backup.sh=20=E2=80=94=20Postgre?= =?UTF-8?q?SQL=20=E6=AF=8F=206h=20=E8=87=AA=E5=8B=95=E5=82=99=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 備份目標 (188): - awoooi_prod (host PostgreSQL, TCP 127.0.0.1) - momo_analytics (momo-db 容器) 功能: - gzip 壓縮,保留 7 天自動清理 - Telegram 通知 (成功/失敗) - cron 0 */6 * * * 已設定 驗證: 兩個 DB 備份成功 (awoooi_prod 206K, gz 完整) Co-Authored-By: Claude Sonnet 4.6 --- scripts/ops/pg-backup.sh | 117 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 scripts/ops/pg-backup.sh diff --git a/scripts/ops/pg-backup.sh b/scripts/ops/pg-backup.sh new file mode 100644 index 00000000..2fab6d6f --- /dev/null +++ b/scripts/ops/pg-backup.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# scripts/ops/pg-backup.sh +# Sprint 5.2: PostgreSQL 自動備份腳本 +# 部署: cron 0 */6 * * * on 188 (ollama user) +# 備份目標: awoooi_prod + momo_analytics +# 保留策略: 7 天 +# 2026-04-09 Claude Sonnet 4.6 Asia/Taipei + +set -euo pipefail + +BACKUP_DIR="${BACKUP_DIR:-/home/ollama/backups}" +SECRETS_FILE="${SECRETS_FILE:-/home/ollama/awoooi-ops-secrets/secrets.env}" +RETAIN_DAYS="${RETAIN_DAYS:-7}" +AWOOOI_API_URL="${AWOOOI_API_URL:-https://awoooi.wooo.work}" + +# 載入 secrets(含 Telegram token for fallback) +[[ -f "$SECRETS_FILE" ]] && source "$SECRETS_FILE" + +TIMESTAMP=$(date '+%Y%m%d_%H%M%S') +LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S %z')]" + +log() { echo "${LOG_PREFIX} $*"; } + +notify_telegram() { + local msg="$1" + if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":\"${msg}\",\"parse_mode\":\"HTML\"}" \ + > /dev/null 2>&1 || true + fi +} + +backup_db() { + local label="$1" # awoooi_prod | momo_analytics + local host="$2" # 127.0.0.1 + local user="$3" + local password="$4" + local dbname="$5" + + local outfile="${BACKUP_DIR}/${label}_${TIMESTAMP}.sql.gz" + log "開始備份 ${label} → ${outfile}" + + if PGPASSWORD="$password" pg_dump \ + -h "$host" -U "$user" -d "$dbname" \ + --no-owner --no-acl \ + 2>/dev/null | gzip > "$outfile"; then + + local size + size=$(du -sh "$outfile" | cut -f1) + log "✅ ${label} 備份完成 (${size})" + echo "success:${label}:${size}" + else + log "❌ ${label} 備份失敗" + echo "failed:${label}" + fi +} + +cleanup_old_backups() { + local label="$1" + local count + count=$(find "$BACKUP_DIR" -name "${label}_*.sql.gz" -mtime "+${RETAIN_DAYS}" | wc -l) + if (( count > 0 )); then + find "$BACKUP_DIR" -name "${label}_*.sql.gz" -mtime "+${RETAIN_DAYS}" -delete + log "🗑️ 清理 ${label} 舊備份 ${count} 個 (>${RETAIN_DAYS}天)" + fi +} + +main() { + mkdir -p "$BACKUP_DIR" + log "=== pg-backup 開始 (retain=${RETAIN_DAYS}d) ===" + + local results=() + + # awoooi_prod (host PostgreSQL, TCP) + results+=("$(backup_db "awoooi_prod" "127.0.0.1" "awoooi" "awoooi_prod_2026" "awoooi_prod")") + + # momo_analytics (momo-db 容器,透過 host TCP) + local momo_pass + momo_pass=$(docker inspect momo-db --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | grep POSTGRES_PASSWORD | cut -d= -f2) + if [[ -n "$momo_pass" ]]; then + results+=("$(backup_db "momo_analytics" "127.0.0.1" "momo" "$momo_pass" "momo_analytics")") + else + log "⚠️ momo-db 密碼未取得,跳過 momo_analytics" + results+=("skipped:momo_analytics") + fi + + # 清理舊備份 + cleanup_old_backups "awoooi_prod" + cleanup_old_backups "momo_analytics" + + log "=== pg-backup 完成 ===" + + # 組裝 Telegram 通知 + local success_count=0 fail_count=0 details="" + for r in "${results[@]}"; do + IFS=':' read -r status label size_or_empty <<< "$r" + case "$status" in + success) ((success_count++)) || true; details+="✅ ${label} (${size_or_empty})\n" ;; + failed) ((fail_count++)) || true; details+="❌ ${label} 失敗\n" ;; + skipped) details+="⏭️ ${label} 跳過\n" ;; + esac + done + + local icon="✅" + [[ $fail_count -gt 0 ]] && icon="⚠️" + + notify_telegram "${icon} AWOOOI DB 備份 +├ 時間: $(date '+%Y-%m-%d %H:%M') +0800 +├ 成功: ${success_count} | 失敗: ${fail_count} +└ ${details}" + + [[ $fail_count -gt 0 ]] && exit 1 + return 0 +} + +main "$@"