Files
2026-07-04 16:00:04 +02:00

227 lines
8.1 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.
#!/bin/bash
# =============================================================================
# postgres_backup.sh
# Backup wszystkich baz PostgreSQL na serwerze
#
# Przeznaczenie: cron (nie interaktywny)
# Wymagania: pg_dump, psql, dostęp do pliku poświadczeń (format pgpass)
#
# Przywracanie pojedynczej bazy:
# psql -h localhost -U postgres -d postgres -f nazwa_bazy.sql
# gunzip -c nazwa_bazy.sql.gz | psql -h localhost -U postgres -d postgres
# =============================================================================
# =============================================================================
# KONFIGURACJA
# =============================================================================
BASE_DIR="/home/admin/postgres_backup"
# Format daty katalogu backupu (składnia date +FORMAT)
DATE_FORMAT="%d-%m-%Y"
# 1 = dodaj godzinę i minuty do nazwy katalogu (np. 19-02-2026_14-35)
ADD_TIME=0
# Liczba dni przechowywania backupów (0 = bez automatycznego usuwania)
RETENTION=7
# 1 = kompresuj do .sql.gz | 0 = zostawiaj jako .sql
ENABLE_COMPRESSION=1
# Plik poświadczeń w formacie pgpass: host:port:database:user:password
# Musi mieć uprawnienia 600 lub 400
CREDENTIALS_FILE="/usr/local/directadmin/conf/postgresql.conf"
# Bazy wykluczone z backupu (oddzielone spacją)
EXCLUDE_DBS="template0 template1"
# =============================================================================
# WEWNĘTRZNA KONFIGURACJA nie zmieniaj
# =============================================================================
LOCK_FILE="/var/run/postgres_backup.lock"
LOG_FILE="/var/log/postgres_backup.log"
# =============================================================================
# INICJALIZACJA
# =============================================================================
set -uo pipefail
# Kolory tylko na terminalu
if [[ -t 1 ]]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
else
RED=''; GREEN=''; YELLOW=''; NC=''
fi
ts() { date '+%Y-%m-%d %H:%M:%S'; }
log() { echo "[$(ts)] $*" | tee -a "$LOG_FILE"; }
ok() { echo -e "${GREEN}[OK]${NC} [$(ts)] $*" | tee -a "$LOG_FILE"; }
warn() { echo -e "${YELLOW}[WARN]${NC} [$(ts)] $*" | tee -a "$LOG_FILE"; }
die() { echo -e "${RED}[BŁĄD]${NC} [$(ts)] $*" | tee -a "$LOG_FILE" >&2; cleanup; exit 1; }
cleanup() { rm -f "$LOCK_FILE"; }
trap cleanup EXIT INT TERM
# ---------------------------------------------------------------------------
# Blokada przed równoległym uruchomieniem
# ---------------------------------------------------------------------------
if [[ -f "$LOCK_FILE" ]]; then
OLD_PID=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
if [[ -n "$OLD_PID" ]] && kill -0 "$OLD_PID" 2>/dev/null; then
die "Backup jest już uruchomiony (PID: ${OLD_PID}). Przerywam."
fi
warn "Stały plik blokady (PID ${OLD_PID} nie istnieje) usuwam i kontynuuję."
rm -f "$LOCK_FILE"
fi
echo $$ > "$LOCK_FILE"
# ---------------------------------------------------------------------------
# Weryfikacja pliku poświadczeń
# ---------------------------------------------------------------------------
[[ -f "$CREDENTIALS_FILE" ]] \
|| die "Brak pliku poświadczeń: ${CREDENTIALS_FILE}"
_perms=$(stat -c '%a' "$CREDENTIALS_FILE")
[[ "$_perms" == "600" || "$_perms" == "400" ]] \
|| die "Plik ${CREDENTIALS_FILE} ma zbyt otwarte uprawnienia (${_perms}). Wymagane: 600 lub 400."
# Parsowanie: host:port:database:user:password
_line=$(head -1 "$CREDENTIALS_FILE")
PGHOST=$(echo "$_line" | cut -d: -f1)
PGPORT=$(echo "$_line" | cut -d: -f2)
PGUSER=$(echo "$_line" | cut -d: -f4)
[[ -n "$PGHOST" && -n "$PGPORT" && -n "$PGUSER" ]] \
|| die "Nieprawidłowy format pliku poświadczeń. Oczekiwany: host:port:database:user:password"
# Hasło przekazywane przez PGPASSFILE nie pojawia się w środowisku ani linii poleceń
export PGPASSFILE="$CREDENTIALS_FILE"
# ---------------------------------------------------------------------------
# Katalog backupu
# ---------------------------------------------------------------------------
if [[ "$ADD_TIME" == "1" ]]; then
DATE_STR=$(date +"${DATE_FORMAT}_%H-%M")
else
DATE_STR=$(date +"${DATE_FORMAT}")
fi
BACKUP_DIR="${BASE_DIR}/${DATE_STR}"
mkdir -p "$BACKUP_DIR" || die "Nie można utworzyć katalogu backupu: ${BACKUP_DIR}"
# =============================================================================
# BACKUP
# =============================================================================
log "================================================================="
log "Rozpoczęcie backupu PostgreSQL"
log "Host: ${PGHOST}:${PGPORT} Użytkownik: ${PGUSER}"
log "Katalog: ${BACKUP_DIR}"
log "Kompresja: ${ENABLE_COMPRESSION} Retencja: ${RETENTION} dni"
log "================================================================="
# ---------------------------------------------------------------------------
# Globals: użytkownicy, role, hasła, przynależności (pg_dumpall --globals-only)
# Wymagane do pełnego disaster recovery przywróć jako pierwsze
# ---------------------------------------------------------------------------
ERRORS=0
BACKED_UP=0
GLOBALS_SUCCESS=true
if [[ "$ENABLE_COMPRESSION" == "1" ]]; then
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql.gz"
pg_dumpall \
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
--no-password --globals-only --clean --if-exists \
2>>"$LOG_FILE" \
| gzip -9 > "$GLOBALS_FILE" \
|| GLOBALS_SUCCESS=false
else
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql"
pg_dumpall \
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
--no-password --globals-only --clean --if-exists \
> "$GLOBALS_FILE" 2>>"$LOG_FILE" \
|| GLOBALS_SUCCESS=false
fi
if [[ "$GLOBALS_SUCCESS" == "true" ]]; then
SIZE=$(du -sh "$GLOBALS_FILE" 2>/dev/null | cut -f1)
ok "_globals → ${GLOBALS_FILE##*/} (${SIZE})"
else
warn "_globals: backup ról i użytkowników nie powiódł się plik usunięty."
rm -f "$GLOBALS_FILE"
ERRORS=$((ERRORS + 1))
fi
# Lista baz danych
DATABASES=$(
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
--no-password -t -A \
-c "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" \
2>>"$LOG_FILE"
) || die "Nie można pobrać listy baz. Sprawdź połączenie i poświadczenia."
# Zbuduj wzorzec wykluczeń
EXCLUDE_PATTERN=$(echo "$EXCLUDE_DBS" | tr ' ' '|')
for DB in $DATABASES; do
# Pomiń wykluczone
if echo "$DB" | grep -qE "^(${EXCLUDE_PATTERN})$"; then
continue
fi
SUCCESS=true
if [[ "$ENABLE_COMPRESSION" == "1" ]]; then
OUT_FILE="${BACKUP_DIR}/${DB}.sql.gz"
pg_dump \
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
--no-password --create --clean --if-exists \
-F p "$DB" 2>>"$LOG_FILE" \
| gzip -9 > "$OUT_FILE" \
|| SUCCESS=false
else
OUT_FILE="${BACKUP_DIR}/${DB}.sql"
pg_dump \
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
--no-password --create --clean --if-exists \
-F p "$DB" > "$OUT_FILE" 2>>"$LOG_FILE" \
|| SUCCESS=false
fi
if [[ "$SUCCESS" == "true" ]]; then
SIZE=$(du -sh "$OUT_FILE" 2>/dev/null | cut -f1)
ok "${DB}${OUT_FILE##*/} (${SIZE})"
BACKED_UP=$((BACKED_UP + 1))
else
warn "${DB}: backup nie powiódł się plik usunięty."
rm -f "$OUT_FILE"
ERRORS=$((ERRORS + 1))
fi
done
log "Wynik: ${BACKED_UP} OK, ${ERRORS} błędów."
# =============================================================================
# RETENCJA
# =============================================================================
if [[ "$RETENTION" -gt 0 ]]; then
log "Usuwanie backupów starszych niż ${RETENTION} dni..."
find "$BASE_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +"$RETENTION" \
-exec rm -rf {} + 2>/dev/null || true
fi
# =============================================================================
# ZAKOŃCZENIE
# =============================================================================
log "Backup zakończony."
[[ "$ERRORS" -eq 0 ]] || exit 1
exit 0