Files
awoooi/scripts/backup/backup-momo-188-pg.sh
Your Name 216b7d78e2
Some checks failed
Code Review / ai-code-review (push) Successful in 11s
Ansible Lint / lint (push) Has been cancelled
fix(backup): 接入 MOMO PG 備份失敗通知
2026-05-12 15:50:44 +08:00

185 lines
5.8 KiB
Bash
Executable File
Raw Permalink 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
# 192.168.0.188 主機層 momo PostgreSQL 備份控制器。
#
# 由 Ansible 部署到:
# /home/ollama/momo-pro/scripts/pg_backup.sh
#
# PostgreSQL 憑證只從 momo-db 容器環境讀取;禁止輸出或落地憑證值。
set -Eeuo pipefail
BACKUP_DIR="${BACKUP_DIR:-/home/ollama/momo_backups}"
DB_CONTAINER="${DB_CONTAINER:-momo-db}"
DB_USER="${DB_USER:-momo}"
DB_NAME="${DB_NAME:-momo_analytics}"
KEEP_DAYS="${KEEP_DAYS:-7}"
MIN_SIZE_BYTES="${MIN_SIZE_BYTES:-1048576}"
LOG_FILE="${LOG_FILE:-${BACKUP_DIR}/backup.log}"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
FILENAME="momo_analytics_${TIMESTAMP}.sql.gz"
FILEPATH="${BACKUP_DIR}/${FILENAME}"
TMP_FILE="${FILEPATH}.tmp"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
START_TS="$(date +%s)"
log() {
local line
line="$(printf '[%s] %s' "$(date '+%Y-%m-%d %H:%M:%S')" "$*")"
printf '%s\n' "$line" >> "${LOG_FILE}"
if [[ "${AWOOI_BACKUP_LOG_STDOUT:-0}" == "1" || -t 1 ]]; then
printf '%s\n' "$line"
fi
}
cleanup_tmp() {
rm -f "${TMP_FILE}"
}
elapsed_seconds() {
local now
now="$(date +%s)"
echo "$((now - START_TS))"
}
notify_awoooi_ops() {
local status="$1"
local summary="$2"
local detail="$3"
local helper="${SCRIPT_DIR}/notify-awoooi-ops.sh"
if [[ "${AWOOI_BACKUP_NOTIFY_ENABLED:-1}" != "1" ]]; then
return 0
fi
[[ -x "$helper" ]] || return 1
AWOOI_OPS_ALERTNAME="Backup.MomoPostgres" \
AWOOI_OPS_JOB_NAME="MOMO PostgreSQL 備份" \
AWOOI_OPS_STATUS="$status" \
AWOOI_OPS_SEVERITY="info" \
AWOOI_OPS_SOURCE="momo-pg-backup" \
AWOOI_OPS_COMPONENT="momo-postgres-backup" \
AWOOI_OPS_SUMMARY="$summary" \
AWOOI_OPS_DETAIL="$detail" \
AWOOI_OPS_DURATION_SECONDS="$(elapsed_seconds)" \
"$helper" >/dev/null
}
notify_best_effort() {
local status="$1"
local summary="$2"
local detail="$3"
if [[ "$status" == "success" && "${AWOOI_BACKUP_NOTIFY_SUCCESS:-0}" != "1" ]]; then
log "略過 AwoooP 成功通知backup-health exporter 作為健康狀態來源"
return 0
fi
notify_awoooi_ops "$status" "$summary" "$detail" || log "WARN AwoooP notification failed"
}
on_error() {
local exit_code="$?"
local line_no="${1:-unknown}"
set +e
notify_awoooi_ops \
"failed" \
"MOMO PostgreSQL 備份失敗" \
"line=${line_no}; container=${DB_CONTAINER}; db=${DB_NAME}; file=${FILENAME}; log=${LOG_FILE}" \
|| true
exit "$exit_code"
}
fail_backup() {
local message="$1"
log "ERROR ${message}"
notify_awoooi_ops \
"failed" \
"MOMO PostgreSQL 備份失敗" \
"${message}; container=${DB_CONTAINER}; db=${DB_NAME}; file=${FILENAME}; log=${LOG_FILE}" \
|| true
exit 1
}
container_running() {
docker inspect -f '{{.State.Running}}' "${DB_CONTAINER}" 2>/dev/null | grep -qx true
}
run_pg_dump() {
docker exec "${DB_CONTAINER}" sh -eu -c '
: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD missing in container env}"
PGPASSWORD="${POSTGRES_PASSWORD}" exec pg_dump \
-U "${POSTGRES_USER:-momo}" \
-d "${POSTGRES_DB:-momo_analytics}" \
--no-password \
--no-owner \
--no-acl
'
}
insert_backup_log() {
local size_bytes="$1"
local duration="$2"
docker exec \
-e BACKUP_FILENAME="${FILENAME}" \
-e BACKUP_SIZE_BYTES="${size_bytes}" \
-e BACKUP_DURATION_SECONDS="${duration}" \
-e BACKUP_HOST="$(hostname)" \
-e BACKUP_STORAGE_PATH="${FILEPATH}" \
"${DB_CONTAINER}" sh -eu -c '
: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD missing in container env}"
PGPASSWORD="${POSTGRES_PASSWORD}" psql \
-U "${POSTGRES_USER:-momo}" \
-d "${POSTGRES_DB:-momo_analytics}" \
--no-password \
-v ON_ERROR_STOP=1 \
-c "INSERT INTO backup_log (filename, file_size_bytes, duration_seconds, status, host, storage_path, completed_at)
VALUES (E'\''${BACKUP_FILENAME}'\'', ${BACKUP_SIZE_BYTES}, ${BACKUP_DURATION_SECONDS}, '\''success'\'', E'\''${BACKUP_HOST}'\'', E'\''${BACKUP_STORAGE_PATH}'\'', CURRENT_TIMESTAMP);"
' >/dev/null 2>&1
}
main() {
mkdir -p "${BACKUP_DIR}"
trap cleanup_tmp EXIT
trap 'on_error ${LINENO}' ERR
log "===== momo PostgreSQL backup start ====="
if ! container_running; then
fail_backup "${DB_CONTAINER} is not running"
fi
local start_ts end_ts duration size_bytes size_human deleted backup_log_status
start_ts="$(date +%s)"
if run_pg_dump | gzip >"${TMP_FILE}"; then
size_bytes="$(stat -c%s "${TMP_FILE}" 2>/dev/null || stat -f%z "${TMP_FILE}" 2>/dev/null || echo 0)"
if [ "${size_bytes}" -lt "${MIN_SIZE_BYTES}" ]; then
fail_backup "backup file too small: ${size_bytes} bytes"
fi
mv "${TMP_FILE}" "${FILEPATH}"
chmod 0640 "${FILEPATH}"
else
fail_backup "pg_dump failed"
fi
end_ts="$(date +%s)"
duration="$((end_ts - start_ts))"
size_human="$(du -h "${FILEPATH}" | awk '{print $1}')"
log "Backup success: ${FILENAME} (${size_human}, ${duration}s)"
backup_log_status="success"
if insert_backup_log "${size_bytes}" "${duration}"; then
log "backup_log insert success"
else
backup_log_status="warning"
log "WARN backup_log insert failed; backup file is still valid"
fi
deleted="$(find "${BACKUP_DIR}" -name 'momo_analytics_*.sql.gz' -mtime "+${KEEP_DAYS}" -print -delete | wc -l | tr -d ' ')"
log "Deleted old backups: ${deleted}"
log "===== momo PostgreSQL backup complete ====="
notify_best_effort \
"success" \
"MOMO PostgreSQL 備份完成" \
"file=${FILENAME}; size=${size_human}; duration=${duration}s; backup_log=${backup_log_status}; deleted_old=${deleted}"
}
main "$@"