From 14274e6260f1c937e81df8556f70f3dcc61c14d9 Mon Sep 17 00:00:00 2001 From: Jens Beckmann Date: Sun, 31 May 2026 17:35:34 +0000 Subject: [PATCH] Dateien nach "scripts" hochladen --- scripts/backup-postgres.sh | 256 +++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 scripts/backup-postgres.sh diff --git a/scripts/backup-postgres.sh b/scripts/backup-postgres.sh new file mode 100644 index 0000000..98c07dd --- /dev/null +++ b/scripts/backup-postgres.sh @@ -0,0 +1,256 @@ +#!/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 "$@"