#!/bin/bash # ============================================================================== # PostgreSQLBackup — záloha PostgreSQL 18 (hlavní instance) na Tower # ============================================================================== # # CO DĚLÁ: # Zálohuje celý PostgreSQL 18 server pomocí pg_dumpall — jeden SQL soubor # obsahuje všechny databáze, role, tablespaces a grants. Výstup je komprimován # gzip přímo ve streamu (bez mezičlánku na disku). # # TECHNICKÉ DETAILY: # - pg_dumpall: záloha celého serveru (ne jen jedné DB) — zahrnuje i role/uživatele # - --clean --if-exists: generuje DROP příkazy před CREATE (bezpečný re-restore) # - Stream přes stdout: docker exec → pipe → gzip → soubor na hostiteli # → žádný volume mount, žádná dočasná kopie uvnitř kontejneru # - ${PIPESTATUS[0]}: exit kód pg_dumpall (ne gzip) — gzip by jinak maskoval chybu # - PGPASSWORD: předáváme jako env var do kontejneru přes -e (ne jako argument) # # SCHEDULE: # Unraid User Script "PostgreSQLBackup" — daily (4:40 ráno) # # STRUKTURA ZÁLOHY: # /mnt/user/Backup/Critical/PostgreSQLBackup/tower/ # YYYY-MM-DD_HHMM/ # all_databases.sql.gz ← komprimovaný pg_dumpall výstup # all_databases.err ← dočasný soubor chyb (smazán při úspěchu) # # VELIKOST / ČAS: # Dump: ~3.3 GB, záloha trvá ~3 minuty # # ROTACE: # Dvoustupňová — nejprve dle stáří (KEEP_DAYS), pak oříznutí na KEEP_COUNT. # # RESTORE: # Viz postgresqlrestore_from_backup.sh # gunzip -c all_databases.sql.gz | docker exec -i postgresql18 psql ... # ============================================================================== set -x # ============================================================================== # KONFIGURACE # ============================================================================== CONTAINER_NAME="postgresql18" # Docker kontejner s PostgreSQL 18 PG_HOST="localhost" # uvnitř kontejneru PG_PORT="5432" # výchozí PG port PG_USER="vladimir.buzalka" # superuser s právy pro pg_dumpall export PGPASSWORD="Vlado7309208104++" # heslo jako env var (pg_dumpall čte PGPASSWORD) UNRAID_NAME="tower" BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup" KEEP_DAYS=7 # zálohy starší než 7 dní se smažou v prvním průchodu KEEP_COUNT=7 # z toho co zbyde, ponech vždy alespoň 7 záloh # ============================================================================== # START # ============================================================================== echo "Starting PostgreSQL full backup (pg_dumpall)" START_TS=$(date '+%Y-%m-%d %H:%M:%S') DATE=$(date +%Y-%m-%d_%H%M) FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DATE" mkdir -p "$FINAL_PATH" DUMP_FILE="$FINAL_PATH/all_databases.sql.gz" # výsledný komprimovaný dump ERR_FILE="$FINAL_PATH/all_databases.err" # stderr pg_dumpall # ============================================================================== # DUMP + GZIP # docker exec spustí pg_dumpall uvnitř kontejneru. # -e PGPASSWORD: předá heslo jako env var do kontejneru (bez -e by pg_dumpall # nemohl autentizovat a skončil by promptem na stdin). # Pipe na gzip: komprimuje stream v reálném čase — na disku nikdy není # nekomprimovaný dump (úspora místa a I/O). # > "$DUMP_FILE": výstup gzip jde do souboru na hostiteli. # 2> "$ERR_FILE": stderr pg_dumpall (warnings, errory) jde do .err souboru. # ============================================================================== docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ pg_dumpall \ --host="$PG_HOST" \ --port="$PG_PORT" \ --username="$PG_USER" \ --clean \ --if-exists \ | gzip > "$DUMP_FILE" 2> "$ERR_FILE" # PIPESTATUS[0] = exit kód prvního příkazu v pipe (pg_dumpall). # Prostý $? by vrátil exit kód gzip — i když pg_dumpall selže, gzip může skončit 0. EXIT_CODE=${PIPESTATUS[0]} # ============================================================================== # VALIDACE # Tři podmínky pro úspěch: # 1. EXIT_CODE=0 — pg_dumpall skončil bez chyby # 2. -s "$DUMP_FILE" — soubor existuje a není prázdný # 3. ! -s "$ERR_FILE" — žádné chybové hlášky (prázdný .err) # ============================================================================== if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then echo "SUCCESS: Full PostgreSQL backup completed" echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" rm -f "$ERR_FILE" else echo "ERROR: Backup failed" echo "Exit code: $EXIT_CODE" echo "Dump file:" ls -lh "$DUMP_FILE" 2>/dev/null || echo " (not created)" echo "Error output:" [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)" fi # ============================================================================== # ROTACE STARÝCH ZÁLOH — dvoustupňová # # Krok 1: Smaž adresáře starší než KEEP_DAYS dní. # -mindepth/-maxdepth 1: jen přímé podadresáře (ne rekurzivně) # # Krok 2: Z toho co zbyde, ponech jen posledních KEEP_COUNT. # Důvod dvou kroků: i při přerušeném schedule garantuje min. KEEP_COUNT záloh. # ============================================================================== echo "Cleaning up old backups..." find "$BASE_PATH/$UNRAID_NAME" \ -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ -exec rm -rf {} \; ls -td "$BASE_PATH/$UNRAID_NAME"/*/ 2>/dev/null \ | tail -n +$((KEEP_COUNT + 1)) \ | xargs -r rm -rf echo "------------------------------------------" echo "Backup task completed at $(date)" echo "Started at: $START_TS" set +x