185 lines
5.8 KiB
Bash
Executable File
185 lines
5.8 KiB
Bash
Executable File
#!/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 "$@"
|