Files
infra-borgbackup-postgresql/scripts/backup-postgres.sh
T
2026-05-31 17:35:34 +00:00

257 lines
7.7 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# borg-backup-postgres.sh
# Pull-basiertes Borg Backup für PostgreSQL via SSH
# Läuft auf dem Raspberry Pi (Backup-Ziel)
# =============================================================================
set -euo pipefail
# --- Konfigurationsdatei laden -----------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/../config/backup.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "[FEHLER] Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
exit 1
fi
# shellcheck source=/dev/null
source "$CONFIG_FILE"
# --- Farben & Logging --------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $*"; }
log_ok() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] ✓${NC} $*"; }
log_warn(){ echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] ⚠${NC} $*"; }
log_err() { echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ✗${NC} $*" >&2; }
# --- Umgebungsvariablen für Borg setzen --------------------------------------
export BORG_PASSPHRASE
export BORG_REPO
export BORG_RSH="ssh -i ${SSH_KEY_PATH} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10"
# --- Fehlerbehandlung --------------------------------------------------------
BACKUP_START=$(date +%s)
FAILED_DBS=()
cleanup() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log_err "Backup mit Fehlercode $exit_code beendet!"
send_notification "FEHLER" "Borg Backup fehlgeschlagen (Exit: $exit_code). Fehlgeschlagene DBs: ${FAILED_DBS[*]:-keine}"
fi
}
trap cleanup EXIT
# --- Hilfsfunktionen ---------------------------------------------------------
check_dependencies() {
local missing=()
for cmd in borg ssh pg_restore; do
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_err "Fehlende Programme: ${missing[*]}"
exit 1
fi
}
check_ssh_connection() {
log "Prüfe SSH-Verbindung zu ${PG_SSH_USER}@${PG_HOST}..."
if ! ssh -i "${SSH_KEY_PATH}" \
-o StrictHostKeyChecking=accept-new \
-o ConnectTimeout=10 \
-o BatchMode=yes \
"${PG_SSH_USER}@${PG_HOST}" "echo ok" &>/dev/null; then
log_err "SSH-Verbindung zu ${PG_HOST} fehlgeschlagen!"
exit 1
fi
log_ok "SSH-Verbindung erfolgreich."
}
check_repo() {
log "Prüfe Borg-Repository: ${BORG_REPO}"
if ! borg info &>/dev/null; then
log_warn "Repository existiert nicht. Initialisiere..."
borg init --encryption=repokey "${BORG_REPO}"
log_ok "Repository initialisiert."
log_warn "WICHTIG: Exportiere den Borg-Key jetzt:"
log_warn " borg key export ${BORG_REPO} ~/borg-key-backup.txt"
else
log_ok "Repository OK."
fi
}
send_notification() {
local status="$1"
local message="$2"
# Nur senden wenn konfiguriert
if [[ -n "${NOTIFY_EMAIL:-}" ]] && command -v mail &>/dev/null; then
echo "$message" | mail -s "[Borg Backup] ${status}" "${NOTIFY_EMAIL}"
fi
# Systemd-Journal / Wall-Nachricht
if [[ "${NOTIFY_WALL:-false}" == "true" ]]; then
wall "[Borg Backup] ${status}: ${message}" 2>/dev/null || true
fi
}
discover_databases() {
log "Frage alle Datenbanken vom Server ab..."
# psql gibt eine Zeile pro DB aus; Systemdatenbanken ausschließen
local list_cmd="sudo -u ${PG_DB_USER} psql -At -c \
\"SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;\""
local all_dbs
all_dbs=$(ssh -i "${SSH_KEY_PATH}" \
-o StrictHostKeyChecking=accept-new \
-o ConnectTimeout=10 \
-o BatchMode=yes \
"${PG_SSH_USER}@${PG_HOST}" \
"${list_cmd}") || {
log_err "Datenbankabfrage fehlgeschlagen!"
exit 1
}
# Ausschlussliste anwenden
local filtered=()
while IFS= read -r db; do
[[ -z "$db" ]] && continue
local excluded=false
for excl in "${PG_EXCLUDE_DATABASES[@]:-}"; do
[[ "$db" == "$excl" ]] && excluded=true && break
done
if $excluded; then
log_warn "Überspringe (ausgeschlossen): ${db}"
else
filtered+=("$db")
fi
done <<< "$all_dbs"
log_ok "Gefundene Datenbanken: ${filtered[*]}"
# Ergebnis per nameref zurückgeben
local -n _result=$1
_result=("${filtered[@]}")
}
backup_database() {
local db="$1"
local archive_name="${db}-$(date +%Y-%m-%dT%H:%M)"
log "Sichere Datenbank: ${db}${archive_name}"
local dump_cmd="sudo -u ${PG_DB_USER} pg_dump --format=custom --no-password ${db}"
if ssh -i "${SSH_KEY_PATH}" \
-o StrictHostKeyChecking=accept-new \
-o ConnectTimeout=10 \
-o BatchMode=yes \
"${PG_SSH_USER}@${PG_HOST}" \
"${dump_cmd}" \
| borg create \
--stdin-name "${db}.pgdump" \
--compression "${BORG_COMPRESSION:-lz4}" \
--stats \
--show-rc \
"${BORG_REPO}::${archive_name}" -; then
log_ok "Datenbank '${db}' erfolgreich gesichert."
else
log_err "Backup von Datenbank '${db}' fehlgeschlagen!"
FAILED_DBS+=("$db")
return 1
fi
}
run_prune() {
log "Bereinige alte Backups (Retention Policy)..."
borg prune \
--keep-daily "${KEEP_DAILY:-7}" \
--keep-weekly "${KEEP_WEEKLY:-4}" \
--keep-monthly "${KEEP_MONTHLY:-6}" \
--glob-archives '*' \
--stats \
--show-rc \
"${BORG_REPO}"
log_ok "Bereinigung abgeschlossen."
}
run_compact() {
# borg compact erst ab Version 1.2 verfügbar
if borg --version | grep -qE "borg 1\.[2-9]|borg [2-9]"; then
log "Kompaktiere Repository..."
borg compact "${BORG_REPO}" && log_ok "Kompaktierung abgeschlossen."
fi
}
# --- Hauptprogramm -----------------------------------------------------------
main() {
log "========================================"
log " Borg PostgreSQL Backup gestartet"
log " Host: ${PG_HOST}"
log " Repo: ${BORG_REPO}"
log "========================================"
check_dependencies
check_ssh_connection
check_repo
# Datenbankliste ermitteln: automatisch oder manuell
local databases=()
if [[ "${PG_BACKUP_ALL:-false}" == "true" ]]; then
discover_databases databases
else
databases=("${PG_DATABASES[@]}")
log "Manuell konfigurierte Datenbanken: ${databases[*]}"
fi
if [[ ${#databases[@]} -eq 0 ]]; then
log_err "Keine Datenbanken zum Sichern gefunden!"
exit 1
fi
# Alle Datenbanken sichern
local success_count=0
for db in "${databases[@]}"; do
if backup_database "$db"; then
((success_count++)) || true
fi
done
# Prune & Compact nur wenn mindestens ein Backup erfolgreich
if [[ $success_count -gt 0 ]]; then
run_prune
run_compact
fi
# Zusammenfassung
local duration=$(( $(date +%s) - BACKUP_START ))
local total=${#databases[@]}
log "========================================"
log_ok "Backup abgeschlossen in ${duration}s"
log_ok "Erfolgreich: ${success_count}/${total} Datenbanken"
if [[ ${#FAILED_DBS[@]} -gt 0 ]]; then
log_err "Fehlgeschlagen: ${FAILED_DBS[*]}"
send_notification "WARNUNG" "${success_count}/${total} DBs gesichert. Fehlgeschlagen: ${FAILED_DBS[*]}"
exit 1
else
send_notification "OK" "Alle ${total} Datenbanken erfolgreich gesichert (${duration}s)."
fi
log "========================================"
}
main "$@"