227 lines
8.1 KiB
Bash
Executable File
227 lines
8.1 KiB
Bash
Executable File
#!/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
|