Files
awoooi/scripts/ops/pg-backup.sh
Your Name 1a74286dfa
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
fix(awooop): mirror ops notifications through api
2026-05-12 14:43:09 +08:00

151 lines
4.8 KiB
Bash
Raw 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.
#!/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}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 載入 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_awoooi_ops() {
local status="$1"
local msg="$2"
local helper="${SCRIPT_DIR}/notify-awoooi-ops.sh"
[[ -x "$helper" ]] || return 1
AWOOI_OPS_ALERTNAME="Backup.PG" \
AWOOI_OPS_JOB_NAME="AWOOOI DB 備份" \
AWOOI_OPS_STATUS="$status" \
AWOOI_OPS_SEVERITY="info" \
AWOOI_OPS_SOURCE="pg-backup" \
AWOOI_OPS_COMPONENT="postgres-backup" \
AWOOI_OPS_SUMMARY="AWOOOI DB 備份 ${status}" \
AWOOI_OPS_DETAIL="$msg" \
"$helper" >/dev/null
}
notify_telegram() {
local msg="$1"
local status="${2:-success}"
# 正式路徑:先交給 AWOOI API由 TelegramGateway 送出並鏡像到 AwoooP。
# 只有 API 不可達或 helper 未部署時,才使用 Telegram 直發救命旁路。
notify_awoooi_ops "$status" "$msg" && return 0
local chat_id="${TELEGRAM_ALERT_CHAT_ID:-${SRE_GROUP_CHAT_ID:--1003711974679}}"
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "$chat_id" ]]; then
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${chat_id}" \
-d "parse_mode=HTML" \
--data-urlencode "text=${msg}" \
> /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 容器,無對外 port用 docker exec 直接 dump)
local outfile_momo="${BACKUP_DIR}/momo_analytics_${TIMESTAMP}.sql.gz"
log "開始備份 momo_analytics (docker exec) → ${outfile_momo}"
if docker exec momo-db pg_dump -U momo momo_analytics 2>/dev/null | gzip > "$outfile_momo"; then
local size_momo
size_momo=$(du -sh "$outfile_momo" | cut -f1)
log "✅ momo_analytics 備份完成 (${size_momo})"
results+=("success:momo_analytics:${size_momo}")
else
log "❌ momo_analytics 備份失敗"
rm -f "$outfile_momo"
results+=("failed: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="⚠️"
local notify_status="success"
[[ $fail_count -gt 0 ]] && notify_status="failed"
notify_telegram "${icon} <b>AWOOOI DB 備份</b>
├ 時間: $(date '+%Y-%m-%d %H:%M') +0800
├ 成功: ${success_count} | 失敗: ${fail_count}
${details}" "$notify_status"
[[ $fail_count -gt 0 ]] && exit 1
return 0
}
main "$@"