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

1372 lines
48 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# postgresql_upgrade.sh
# Aktualizacja PostgreSQL (major upgrade) na AlmaLinux/CloudLinux/RHEL-like
# z zachowaniem danych, ról, użytkowników i haseł:
# - tryb pg_upgrade
# - tryb backup + odtworzenie do nowego klastra
# =============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
sekcja() { echo -e "\n${BOLD}${CYAN}=== $* ===${NC}\n"; }
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
CB_PHP_WEBSERVER=""
CB_PHP_OPTIONS_CONF=""
declare -ag CB_PHP_VERSIONS=()
declare -ag CB_PHP_CODES=()
declare -ag CB_PHP_MODES=()
declare -ag PHP_FILES_TO_UPDATE=()
declare -ag PHP_VERSIONS_TO_REBUILD=()
DA_PG_RUNTIME_LIBDIR=""
DA_PG_RUNTIME_LIBPQ=""
cb_pgsql_reset_php_targets() {
CB_PHP_VERSIONS=()
CB_PHP_CODES=()
CB_PHP_MODES=()
}
cb_pgsql_get_cb_option() {
local options_conf="$1"
local key="$2"
local value=""
[[ -f "$options_conf" ]] || return 1
value="$(awk -F= -v key="$key" '
$1 == key {
sub(/^[[:space:]]+/, "", $2)
sub(/[[:space:]]+$/, "", $2)
print $2
exit
}
' "$options_conf")"
[[ -n "$value" ]] || return 1
printf '%s\n' "$value"
}
cb_pgsql_php_code_from_version() {
printf '%s\n' "${1//./}"
}
cb_pgsql_php_version_from_code() {
local code="$1"
if [[ "$code" == *.* ]]; then
printf '%s\n' "$code"
elif [[ "$code" =~ ^[0-9]{2,3}$ ]]; then
printf '%s.%s\n' "${code:0:1}" "${code:1}"
else
printf '%s\n' "$code"
fi
}
cb_pgsql_add_php_target() {
local version="$1"
local mode="$2"
local code=""
local idx=""
[[ -n "$version" ]] || return 0
code="$(cb_pgsql_php_code_from_version "$version")"
for idx in "${!CB_PHP_CODES[@]}"; do
if [[ "${CB_PHP_CODES[$idx]}" == "$code" ]]; then
return 0
fi
done
CB_PHP_VERSIONS+=("$version")
CB_PHP_CODES+=("$code")
CB_PHP_MODES+=("${mode:-php-fpm}")
}
cb_pgsql_detect_webserver() {
local cb_dir="$1"
CB_PHP_OPTIONS_CONF="${cb_dir}/options.conf"
CB_PHP_WEBSERVER="apache"
if [[ -f "$CB_PHP_OPTIONS_CONF" ]]; then
CB_PHP_WEBSERVER="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "webserver" || true)"
[[ -n "$CB_PHP_WEBSERVER" ]] || CB_PHP_WEBSERVER="apache"
fi
}
cb_pgsql_collect_php_targets() {
local cb_dir="$1"
local slot=""
local release=""
local mode=""
local default_mode=""
cb_pgsql_reset_php_targets
cb_pgsql_detect_webserver "$cb_dir"
# CloudLinux może używać lsphp także przy webserver=apache.
# Dlatego najpierw honorujemy phpN_mode/php1_mode z options.conf,
# a dopiero przy ich braku stosujemy domyślny tryb wynikający z webserwera.
case "$CB_PHP_WEBSERVER" in
litespeed|openlitespeed) default_mode="lsphp" ;;
*) default_mode="php-fpm" ;;
esac
for slot in 1 2 3 4 5 6 7 8 9; do
release="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_release" || true)"
mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_mode" || true)"
[[ -n "$mode" ]] || mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php1_mode" || true)"
[[ -n "$mode" ]] || mode="$default_mode"
case "${release,,}" in
""|no|off) continue ;;
esac
case "${mode,,}" in
no|off) continue ;;
esac
cb_pgsql_add_php_target "$release" "$mode"
done
}
cb_pgsql_build_file_candidates() {
local cb_dir="$1"
local mode="$2"
local code="$3"
local -n out_ref="$4"
out_ref=()
if [[ "$mode" == "lsphp" ]]; then
case "$CB_PHP_WEBSERVER" in
litespeed)
out_ref+=("${cb_dir}/configure/litespeed/configure.php${code}")
;;
openlitespeed)
out_ref+=("${cb_dir}/configure/openlitespeed/configure.php${code}")
;;
esac
out_ref+=("${cb_dir}/configure/lsphp/configure.lsphp${code}")
out_ref+=("${cb_dir}/configure/lsphp/configure.php${code}")
fi
out_ref+=("${cb_dir}/configure/php/configure.php${code}")
}
cb_pgsql_resolve_config_paths() {
local cb_dir="$1"
local version="$2"
local mode="$3"
local source_ref_name="$4"
local custom_ref_name="$5"
local code=""
local -a candidates=()
local candidate=""
code="$(cb_pgsql_php_code_from_version "$version")"
printf -v "$source_ref_name" '%s' ""
printf -v "$custom_ref_name" '%s' ""
cb_pgsql_build_file_candidates "$cb_dir" "$mode" "$code" candidates
for candidate in "${candidates[@]}"; do
if [[ -f "$candidate" ]]; then
printf -v "$source_ref_name" '%s' "$candidate"
printf -v "$custom_ref_name" '%s' "$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
return 0
fi
done
for candidate in "${candidates[@]}"; do
candidate="$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
if [[ -f "$candidate" ]]; then
printf -v "$source_ref_name" '%s' "$candidate"
printf -v "$custom_ref_name" '%s' "$candidate"
return 0
fi
done
return 1
}
cb_pgsql_configure_runtime_libpq() {
local pg_prefix="$1"
local pg_libdir="${pg_prefix}/lib"
local libpq_so="${pg_libdir}/libpq.so.5"
local ld_conf="/etc/ld.so.conf.d/00-da-postgresql-libpq.conf"
local old_ld_conf="/etc/ld.so.conf.d/da-postgresql-libpq.conf"
local preferred=""
[[ -d "$pg_libdir" ]] || die "Nie znaleziono katalogu bibliotek PostgreSQL: ${pg_libdir}"
[[ -r "$libpq_so" ]] || die "Nie znaleziono biblioteki libpq: ${libpq_so}"
printf '%s\n' "$pg_libdir" > "$ld_conf"
chmod 644 "$ld_conf"
[[ "$old_ld_conf" == "$ld_conf" ]] || rm -f "$old_ld_conf"
if command -v ldconfig >/dev/null 2>&1; then
ldconfig || die "Nie udało się odświeżyć cache bibliotek przez ldconfig."
preferred="$(ldconfig -p 2>/dev/null | awk '/libpq\.so\.5/{print $NF; exit}')"
if [[ -n "$preferred" && "$preferred" != "$libpq_so" ]]; then
warn "Domyślna libpq to ${preferred}; wymuszę właściwą bibliotekę przez LD_PRELOAD podczas kompilacji PHP."
fi
else
warn "Brak ldconfig. Kontynuuję z LD_LIBRARY_PATH."
fi
DA_PG_RUNTIME_LIBDIR="$pg_libdir"
DA_PG_RUNTIME_LIBPQ="$libpq_so"
info "Skonfigurowano bibliotekę runtime PostgreSQL: ${pg_libdir}"
}
[[ $EUID -eq 0 ]] || die "Skrypt musi być uruchomiony jako root."
LOGFILE="/var/log/postgresql_upgrade_$(date +%Y%m%d_%H%M%S).log"
exec 1> >(tee >(sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> "$LOGFILE")) 2>&1
info "Pełny log zapisany w: $LOGFILE"
# Konfiguracja backupów przed aktualizacją:
# POSTGRESQL_DUMP_BASEDIR - katalog bazowy dla dumpów
# DATE_FORMAT - format daty (domyślnie dzień-miesiąc-rok)
POSTGRESQL_DUMP_BASEDIR="${POSTGRESQL_DUMP_BASEDIR:-/opt/postgres_upgrade_backups}"
DATE_FORMAT="${DATE_FORMAT:-%d-%m-%Y}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CB_DIR="/usr/local/directadmin/custombuild"
SUPPORTED_TARGETS=(14 15 16 17 18)
INSTALLED_VERSIONS=()
CURRENT_VERSION=""
TARGET_VERSION=""
CHOSEN_VERSION=""
DO_BACKUP="tak"
PHP_UPDATE_NEEDED="nie"
BACKUP_DIR=""
BACKUP_RESULT="nie wykonano"
BACKUP_GLOBALS_FILE=""
BACKUP_CLUSTER_FILE=""
UPGRADE_MODE="pg_upgrade"
REQUIRE_LOGICAL_BACKUP="0"
DELETE_OLD_CLUSTER_CMD=""
REMOVE_OLD_SCRIPT_PATH="/opt/remove_old_postgres.sh"
REMOVE_OLD_SCRIPT_RESULT="nie wygenerowano"
INITDB_SUPERUSER="postgres"
RESTORE_MAINT_DB=""
OLD_SERVICE=""
NEW_SERVICE=""
OLD_BINDIR=""
NEW_BINDIR=""
OLD_DATADIR=""
NEW_DATADIR=""
OLD_CONF=""
NEW_CONF=""
OLD_HBA=""
NEW_HBA=""
OLD_PREFIX=""
NEW_PREFIX=""
OLD_CHECKSUM_VERSION=""
OLD_CHECKSUMS=""
NEW_INITDB_CHECKSUM_FLAG=""
PG_UPGRADE_WORKDIR=""
OS_MAJOR=""
PGHOST_VAL=""
PGPORT_VAL=""
PGUSER_VAL=""
PGPASSWORD_VAL=""
CRED_FILE="/usr/local/directadmin/conf/postgresql.conf"
ask_yes_no() {
local prompt="$1"
local default="${2:-t}" # t/n
local answer=""
while true; do
if [[ "$default" == "t" ]]; then
read -rp "${prompt} [T/n]: " answer
answer="${answer:-t}"
else
read -rp "${prompt} [t/N]: " answer
answer="${answer:-n}"
fi
case "${answer,,}" in
t|tak|y|yes) return 0 ;;
n|nie|no) return 1 ;;
*) warn "Podaj t lub n." ;;
esac
done
}
load_pg_credentials() {
sekcja "Wczytywanie poświadczeń PostgreSQL"
[[ -r "$CRED_FILE" ]] || die "Brak odczytu pliku poświadczeń: ${CRED_FILE}"
local line rest
line="$(grep -Ev '^[[:space:]]*($|#)' "$CRED_FILE" | head -n1 || true)"
[[ -n "$line" ]] || die "Plik poświadczeń jest pusty: ${CRED_FILE}"
PGHOST_VAL=""
PGPORT_VAL=""
PGUSER_VAL=""
PGPASSWORD_VAL=""
if [[ "$line" == *=* ]]; then
while IFS='=' read -r key value; do
key="$(echo "$key" | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]')"
value="$(echo "$value" | sed -E 's/[[:space:]]+#.*$//' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
case "$key" in
PG_HOST) PGHOST_VAL="$value" ;;
PG_PORT) PGPORT_VAL="$value" ;;
PG_USER) PGUSER_VAL="$value" ;;
PG_PASSWORD) PGPASSWORD_VAL="$value" ;;
esac
done < <(grep -Ev '^[[:space:]]*($|#)' "$CRED_FILE")
else
PGHOST_VAL="${line%%:*}"
rest="${line#*:}"
PGPORT_VAL="${rest%%:*}"
rest="${rest#*:}"
rest="${rest#*:}"
PGUSER_VAL="${rest%%:*}"
PGPASSWORD_VAL="${rest#*:}"
fi
[[ -n "$PGHOST_VAL" ]] || PGHOST_VAL="localhost"
[[ -n "$PGPORT_VAL" ]] || PGPORT_VAL="5432"
[[ -n "$PGUSER_VAL" ]] || PGUSER_VAL="postgres"
[[ -n "$PGPASSWORD_VAL" ]] || die "Brak hasła PostgreSQL w pliku: ${CRED_FILE}"
info "Poświadczenia PostgreSQL wczytane z: ${CRED_FILE}"
}
contains_target_version() {
local needle="$1"
local v
for v in "${SUPPORTED_TARGETS[@]}"; do
[[ "$v" == "$needle" ]] && return 0
done
return 1
}
detect_os() {
sekcja "Wykrywanie systemu operacyjnego"
[[ -f /etc/os-release ]] || die "Nie można wykryć systemu: brak /etc/os-release."
# shellcheck disable=SC1091
source /etc/os-release
OS_MAJOR="${VERSION_ID%%.*}"
case "${ID:-}" in
almalinux|cloudlinux|centos|rhel|rocky|ol) ;;
*)
die "Nieobsługiwany system: ${ID:-unknown}. Obsługiwane: AlmaLinux/CloudLinux/RHEL-like."
;;
esac
[[ "$OS_MAJOR" == "8" || "$OS_MAJOR" == "9" ]] \
|| die "Nieobsługiwana wersja główna systemu: ${OS_MAJOR}. Obsługiwane EL8/EL9."
info "Wykryto system: ${PRETTY_NAME:-$ID} (EL${OS_MAJOR})"
}
detect_installed_versions() {
sekcja "Wykrywanie zainstalowanych wersji PostgreSQL"
local d v
for d in /usr/pgsql-*; do
[[ -d "$d" ]] || continue
v="${d#/usr/pgsql-}"
[[ "$v" =~ ^[0-9]+$ ]] || continue
[[ -x "$d/bin/pg_upgrade" || -x "$d/bin/postgres" ]] || continue
INSTALLED_VERSIONS+=("$v")
done
if [[ ${#INSTALLED_VERSIONS[@]} -eq 0 ]]; then
die "Nie wykryto zainstalowanych wersji PostgreSQL w /usr/pgsql-*."
fi
mapfile -t INSTALLED_VERSIONS < <(printf '%s\n' "${INSTALLED_VERSIONS[@]}" | sort -n | uniq)
info "Wykryte wersje: $(IFS=', '; echo "${INSTALLED_VERSIONS[*]}")"
}
choose_from_versions() {
local title="$1"
local -n _arr_ref="$2"
local default="$3"
local pick=""
echo "$title"
local i=1
local v
for v in "${_arr_ref[@]}"; do
echo " [$i] PostgreSQL $v"
((i++))
done
while true; do
read -rp "Wybierz [1-${#_arr_ref[@]}, domyślnie ${default}]: " pick
pick="${pick:-$default}"
if [[ "$pick" =~ ^[0-9]+$ ]] && (( pick >= 1 && pick <= ${#_arr_ref[@]} )); then
CHOSEN_VERSION="${_arr_ref[$((pick-1))]}"
return 0
fi
warn "Nieprawidłowy wybór."
done
}
detect_current_version() {
sekcja "Wybór aktualnej wersji PostgreSQL"
local active_versions=()
local v
for v in "${INSTALLED_VERSIONS[@]}"; do
if systemctl is-active --quiet "postgresql-${v}"; then
active_versions+=("$v")
fi
done
if [[ ${#active_versions[@]} -eq 1 ]]; then
CURRENT_VERSION="${active_versions[0]}"
info "Wykryto aktywną wersję: PostgreSQL ${CURRENT_VERSION}"
return 0
fi
if [[ ${#active_versions[@]} -gt 1 ]]; then
warn "Wykryto więcej niż jedną aktywną wersję PostgreSQL."
choose_from_versions "Wskaż wersję źródłową do aktualizacji:" active_versions 1
CURRENT_VERSION="$CHOSEN_VERSION"
info "Wybrano wersję źródłową: PostgreSQL ${CURRENT_VERSION}"
return 0
fi
if [[ ${#INSTALLED_VERSIONS[@]} -eq 1 ]]; then
CURRENT_VERSION="${INSTALLED_VERSIONS[0]}"
info "Wykryto jedną wersję: PostgreSQL ${CURRENT_VERSION}"
return 0
fi
choose_from_versions "Brak aktywnej usługi. Wskaż wersję źródłową:" INSTALLED_VERSIONS 1
CURRENT_VERSION="$CHOSEN_VERSION"
info "Wybrano wersję źródłową: PostgreSQL ${CURRENT_VERSION}"
}
choose_target_version() {
sekcja "Wybór wersji docelowej PostgreSQL"
local targets=()
local v
for v in "${SUPPORTED_TARGETS[@]}"; do
if (( v > CURRENT_VERSION )); then
targets+=("$v")
fi
done
[[ ${#targets[@]} -gt 0 ]] || die "Brak dostępnej nowszej wersji docelowej."
echo "Dostępne wersje docelowe:"
local idx=1
for v in "${targets[@]}"; do
echo " [$idx] PostgreSQL $v"
((idx++))
done
echo " [$idx] Inna (podaj ręcznie numer wersji)"
while true; do
local pick
read -rp "Wybierz opcję [1-${idx}, domyślnie 1]: " pick
pick="${pick:-1}"
if [[ ! "$pick" =~ ^[0-9]+$ ]] || (( pick < 1 || pick > idx )); then
warn "Podaj liczbę od 1 do ${idx}."
continue
fi
if (( pick < idx )); then
TARGET_VERSION="${targets[$((pick-1))]}"
break
fi
while true; do
read -rp "Podaj ręcznie numer wersji PostgreSQL (np. 16 lub 17): " TARGET_VERSION
if [[ ! "$TARGET_VERSION" =~ ^[0-9]+$ ]]; then
warn "Dozwolone są tylko liczby całkowite, np. 16 lub 17."
continue
fi
if (( TARGET_VERSION <= CURRENT_VERSION )); then
warn "Docelowa wersja musi być wyższa niż ${CURRENT_VERSION}."
continue
fi
break
done
break
done
info "Wybrano docelową wersję: PostgreSQL ${TARGET_VERSION}"
}
choose_upgrade_mode() {
sekcja "Wybór trybu aktualizacji"
echo " [1] pg_upgrade (szybszy, zachowuje strukturę klastra)"
echo " [2] Backup + odtworzenie (nowy klaster + restore dumpa)"
local pick=""
while true; do
read -rp "Wybierz tryb [1-2, domyślnie 1]: " pick
pick="${pick:-1}"
case "$pick" in
1)
UPGRADE_MODE="pg_upgrade"
REQUIRE_LOGICAL_BACKUP="0"
INITDB_SUPERUSER="postgres"
RESTORE_MAINT_DB=""
info "Wybrano tryb: pg_upgrade"
return 0
;;
2)
UPGRADE_MODE="backup_restore"
REQUIRE_LOGICAL_BACKUP="1"
INITDB_SUPERUSER="postgres"
RESTORE_MAINT_DB="da_restore_maint_$(date +%Y%m%d%H%M%S)"
info "Wybrano tryb: backup + odtworzenie"
info "Superuser initdb: ${INITDB_SUPERUSER}"
info "Tymczasowa baza serwisowa restore: ${RESTORE_MAINT_DB}"
return 0
;;
*)
warn "Podaj 1 lub 2."
;;
esac
done
}
ensure_sudo_available() {
sekcja "Weryfikacja narzędzia sudo"
if command -v sudo >/dev/null 2>&1; then
info "sudo jest już zainstalowane."
return 0
fi
warn "Brak sudo. Instaluję pakiet sudo..."
dnf install -y sudo || die "Nie udało się zainstalować sudo."
command -v sudo >/dev/null 2>&1 || die "sudo nadal nie jest dostępne po instalacji."
info "Zainstalowano sudo."
}
prepare_paths() {
OLD_SERVICE="postgresql-${CURRENT_VERSION}"
NEW_SERVICE="postgresql-${TARGET_VERSION}"
OLD_BINDIR="/usr/pgsql-${CURRENT_VERSION}/bin"
NEW_BINDIR="/usr/pgsql-${TARGET_VERSION}/bin"
OLD_DATADIR="/var/lib/pgsql/${CURRENT_VERSION}/data"
NEW_DATADIR="/var/lib/pgsql/${TARGET_VERSION}/data"
OLD_CONF="${OLD_DATADIR}/postgresql.conf"
NEW_CONF="${NEW_DATADIR}/postgresql.conf"
OLD_HBA="${OLD_DATADIR}/pg_hba.conf"
NEW_HBA="${NEW_DATADIR}/pg_hba.conf"
OLD_PREFIX="/usr/pgsql-${CURRENT_VERSION}"
NEW_PREFIX="/usr/pgsql-${TARGET_VERSION}"
[[ -x "${OLD_BINDIR}/pg_upgrade" ]] || die "Brak narzędzi PostgreSQL dla wersji źródłowej: ${OLD_BINDIR}"
[[ -d "$OLD_DATADIR" ]] || die "Brak katalogu danych źródłowych: ${OLD_DATADIR}"
[[ -f "$OLD_CONF" ]] || die "Brak pliku: ${OLD_CONF}"
[[ -f "$OLD_HBA" ]] || die "Brak pliku: ${OLD_HBA}"
}
detect_old_cluster_checksum_mode() {
sekcja "Wykrywanie ustawień data checksums starego klastra"
local controldata_bin="${OLD_BINDIR}/pg_controldata"
[[ -x "$controldata_bin" ]] || die "Brak narzędzia pg_controldata: ${controldata_bin}"
local ctl_out
ctl_out="$(sudo -u postgres "$controldata_bin" "$OLD_DATADIR" 2>/dev/null || true)"
[[ -n "$ctl_out" ]] || die "Nie udało się odczytać metadanych starego klastra (${OLD_DATADIR})."
local version_raw
version_raw="$(printf '%s\n' "$ctl_out" | awk -F: '
BEGIN { IGNORECASE=1 }
/checksum/ && /version/ {
v=$2
gsub(/^[ \t]+|[ \t]+$/, "", v)
if (v ~ /^[0-9]+$/) {
print v
exit
}
}
')"
if [[ -n "$version_raw" ]]; then
OLD_CHECKSUM_VERSION="$version_raw"
if [[ "$OLD_CHECKSUM_VERSION" == "0" ]]; then
OLD_CHECKSUMS="off"
NEW_INITDB_CHECKSUM_FLAG="--no-data-checksums"
else
OLD_CHECKSUMS="on"
NEW_INITDB_CHECKSUM_FLAG="--data-checksums"
fi
else
local checksums_raw
checksums_raw="$(printf '%s\n' "$ctl_out" | awk -F: '
BEGIN { IGNORECASE=1 }
/data[[:space:]]+checksums/ {
v=$2
gsub(/^[ \t]+|[ \t]+$/, "", v)
print tolower(v)
exit
}
')"
case "$checksums_raw" in
on)
OLD_CHECKSUM_VERSION="1"
OLD_CHECKSUMS="on"
NEW_INITDB_CHECKSUM_FLAG="--data-checksums"
;;
off)
OLD_CHECKSUM_VERSION="0"
OLD_CHECKSUMS="off"
NEW_INITDB_CHECKSUM_FLAG="--no-data-checksums"
;;
*)
die "Nie udało się ustalić trybu data checksums starego klastra."
;;
esac
fi
info "Stary klaster checksums: ${OLD_CHECKSUMS} (version=${OLD_CHECKSUM_VERSION})"
info "Nowy klaster zostanie zainicjalizowany flagą: ${NEW_INITDB_CHECKSUM_FLAG}"
}
detect_php_update_need() {
sekcja "Analiza konfiguracji PHP (configure.phpXX)"
PHP_FILES_TO_UPDATE=()
PHP_VERSIONS_TO_REBUILD=()
PHP_UPDATE_NEEDED="nie"
cb_pgsql_collect_php_targets "$CB_DIR"
local idx=""
local source_conf=""
local custom_conf=""
local scan_file=""
for idx in "${!CB_PHP_VERSIONS[@]}"; do
if ! cb_pgsql_resolve_config_paths "$CB_DIR" "${CB_PHP_VERSIONS[$idx]}" "${CB_PHP_MODES[$idx]}" source_conf custom_conf; then
continue
fi
scan_file="$custom_conf"
if [[ "$scan_file" != "$source_conf" && ! -f "$scan_file" ]]; then
scan_file="$source_conf"
fi
[[ -f "$scan_file" ]] || continue
if grep -qE -- "--with-pgsql=${OLD_PREFIX}|--with-pdo-pgsql=${OLD_PREFIX}" "$scan_file"; then
PHP_FILES_TO_UPDATE+=("$scan_file")
PHP_VERSIONS_TO_REBUILD+=("${CB_PHP_VERSIONS[$idx]}")
fi
done
if [[ ${#PHP_FILES_TO_UPDATE[@]} -gt 0 ]]; then
PHP_UPDATE_NEEDED="tak"
info "Wykryto pliki do aktualizacji ścieżek PostgreSQL: ${#PHP_FILES_TO_UPDATE[@]}"
else
info "Nie wykryto plików configure PHP wymagających zmian."
fi
}
print_summary_and_confirm() {
sekcja "Podsumowanie operacji"
printf "Aktualna wersja PostgreSQL:\t%s\n" "$CURRENT_VERSION"
printf "Docelowa wersja PostgreSQL:\t%s\n" "$TARGET_VERSION"
printf "Tryb aktualizacji:\t%s\n" "$UPGRADE_MODE"
if [[ "$UPGRADE_MODE" == "backup_restore" ]]; then
printf "Superuser initdb:\t%s\n" "$INITDB_SUPERUSER"
printf "Baza serwisowa restore (tymczasowa):\t%s\n" "$RESTORE_MAINT_DB"
fi
printf "Aktualny katalog danych:\t%s\n" "$OLD_DATADIR"
printf "Docelowy katalog danych:\t%s\n" "$NEW_DATADIR"
printf "Wykonanie backupu istniejących baz danych:\t%s\n" "$DO_BACKUP"
printf "Aktualizacja PHP:\t%s\n" "$PHP_UPDATE_NEEDED"
echo ""
if [[ "$PHP_UPDATE_NEEDED" == "tak" ]]; then
echo "Pliki configure do aktualizacji:"
local f
for f in "${PHP_FILES_TO_UPDATE[@]}"; do
echo " - $f"
done
echo ""
fi
ask_yes_no "Czy kontynuować aktualizację?" "n" || die "Przerwano przez użytkownika."
}
install_target_packages() {
sekcja "Instalacja pakietów PostgreSQL ${TARGET_VERSION}"
local pgdg_rpm="https://download.postgresql.org/pub/repos/yum/reporpms/EL-${OS_MAJOR}-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
if ! rpm -q pgdg-redhat-repo &>/dev/null; then
info "Instaluję repozytorium PGDG."
dnf install -y "$pgdg_rpm" || die "Nie udało się zainstalować repozytorium PGDG."
fi
dnf -qy module disable postgresql 2>/dev/null || true
dnf install -y \
"postgresql${TARGET_VERSION}-server" \
"postgresql${TARGET_VERSION}-contrib" \
"postgresql${TARGET_VERSION}" \
"postgresql${TARGET_VERSION}-libs" \
"postgresql${TARGET_VERSION}-devel" \
|| die "Instalacja pakietów PostgreSQL ${TARGET_VERSION} nie powiodła się."
}
ensure_old_service_running_for_backup() {
if systemctl is-active --quiet "$OLD_SERVICE"; then
return 0
fi
warn "Usługa ${OLD_SERVICE} jest zatrzymana. Uruchamiam tymczasowo do wykonania backupu."
if systemctl start "$OLD_SERVICE"; then
return 0
fi
if systemctl is-active --quiet "$OLD_SERVICE"; then
return 0
fi
return 1
}
run_offline_cluster_backup() {
local backup_stamp="$1"
local data_archive="${BACKUP_DIR}/physical_cluster_${backup_stamp}.tar.gz"
local config_archive="${BACKUP_DIR}/config_${backup_stamp}.tar.gz"
[[ -d "$OLD_DATADIR" ]] || die "Brak katalogu danych źródłowych do backupu offline: ${OLD_DATADIR}"
[[ -f "$OLD_CONF" ]] || die "Brak pliku konfiguracyjnego do backupu offline: ${OLD_CONF}"
[[ -f "$OLD_HBA" ]] || die "Brak pliku pg_hba do backupu offline: ${OLD_HBA}"
if systemctl is-active --quiet "$OLD_SERVICE"; then
die "Backup offline wymaga zatrzymanej usługi ${OLD_SERVICE}."
fi
warn "Nie udało się uruchomić ${OLD_SERVICE}. Tworzę fallback: fizyczny backup offline."
info "Tworzę archiwum katalogu danych starego klastra..."
tar -C "$(dirname "$OLD_DATADIR")" -czf "$data_archive" "$(basename "$OLD_DATADIR")" \
|| die "Nie udało się utworzyć archiwum danych starego klastra."
info "Tworzę archiwum plików konfiguracyjnych starego klastra..."
tar -czf "$config_archive" "$OLD_CONF" "$OLD_HBA" \
|| die "Nie udało się utworzyć archiwum konfiguracji starego klastra."
chmod 600 "$data_archive" "$config_archive" || true
BACKUP_RESULT="fizyczny offline (tar)"
warn "Utworzono backup fizyczny offline zamiast backupu logicznego pg_dumpall."
}
run_full_backup() {
[[ "$DO_BACKUP" == "tak" ]] || return 0
sekcja "Wykonywanie pełnego backupu klastra PostgreSQL"
BACKUP_GLOBALS_FILE=""
BACKUP_CLUSTER_FILE=""
local backup_date
local backup_stamp
backup_date="$(date +"${DATE_FORMAT}")"
backup_stamp="$(date +%H%M%S)"
BACKUP_DIR="${POSTGRESQL_DUMP_BASEDIR}/postgresql-${CURRENT_VERSION}/${backup_date}"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
local old_pg_dumpall="${OLD_BINDIR}/pg_dumpall"
[[ -x "$old_pg_dumpall" ]] || die "Brak narzędzia: ${old_pg_dumpall}"
if ! ensure_old_service_running_for_backup; then
if [[ "$REQUIRE_LOGICAL_BACKUP" == "1" ]]; then
die "Wybrany tryb wymaga backupu logicznego, ale nie można uruchomić ${OLD_SERVICE}."
fi
run_offline_cluster_backup "$backup_stamp"
info "Backup zapisany w: ${BACKUP_DIR}"
return 0
fi
local globals_sql="${BACKUP_DIR}/globals_${backup_stamp}.sql"
local full_sql="${BACKUP_DIR}/full_cluster_${backup_stamp}.sql"
local full_dump_opts=(--no-password)
if [[ "$UPGRADE_MODE" == "backup_restore" ]]; then
# Zalecenie z dokumentacji pg_dumpall:
# dla restore na nowym klastrze używamy --clean --if-exists.
full_dump_opts+=(--clean --if-exists)
fi
info "Tworzę backup globalny (role/użytkownicy/uprawnienia)..."
sudo -u postgres env \
PGHOST="$PGHOST_VAL" \
PGPORT="$PGPORT_VAL" \
PGUSER="$PGUSER_VAL" \
PGPASSWORD="$PGPASSWORD_VAL" \
"$old_pg_dumpall" --no-password --globals-only > "$globals_sql"
if [[ "$UPGRADE_MODE" == "backup_restore" ]]; then
info "Tworzę pełny backup wszystkich baz (tryb restore: --clean --if-exists)..."
else
info "Tworzę pełny backup wszystkich baz..."
fi
sudo -u postgres env \
PGHOST="$PGHOST_VAL" \
PGPORT="$PGPORT_VAL" \
PGUSER="$PGUSER_VAL" \
PGPASSWORD="$PGPASSWORD_VAL" \
"$old_pg_dumpall" "${full_dump_opts[@]}" > "$full_sql"
gzip -f "$globals_sql" "$full_sql"
BACKUP_GLOBALS_FILE="${globals_sql}.gz"
BACKUP_CLUSTER_FILE="${full_sql}.gz"
BACKUP_RESULT="logiczny (pg_dumpall)"
info "Backup zapisany w: ${BACKUP_DIR}"
}
init_new_cluster() {
sekcja "Inicjalizacja nowego klastra PostgreSQL ${TARGET_VERSION}"
[[ -n "$NEW_INITDB_CHECKSUM_FLAG" ]] || die "Brak ustawionego trybu checksums dla nowego klastra."
if [[ -f "${NEW_DATADIR}/PG_VERSION" ]]; then
warn "Docelowy katalog danych już istnieje: ${NEW_DATADIR}"
if ask_yes_no "Usunąć istniejący klaster docelowy i zainicjalizować ponownie?" "n"; then
[[ "$NEW_DATADIR" == /var/lib/pgsql/*/data ]] || die "Niebezpieczna ścieżka NEW_DATADIR: ${NEW_DATADIR}"
systemctl stop "$NEW_SERVICE" >/dev/null 2>&1 || true
rm -rf "${NEW_DATADIR:?}/"*
info "Wyczyszczono istniejący katalog danych: ${NEW_DATADIR}"
else
die "Docelowy katalog danych już istnieje: ${NEW_DATADIR}. Przerwano."
fi
fi
if [[ ! -d "$NEW_DATADIR" ]]; then
mkdir -p "$NEW_DATADIR"
fi
chown postgres:postgres "$NEW_DATADIR"
chmod 700 "$NEW_DATADIR"
local initdb_bin="${NEW_BINDIR}/initdb"
[[ -x "$initdb_bin" ]] || die "Brak narzędzia initdb: ${initdb_bin}"
local initdb_args=("-D" "$NEW_DATADIR" "$NEW_INITDB_CHECKSUM_FLAG" "-U" "$INITDB_SUPERUSER")
sudo -u postgres "$initdb_bin" "${initdb_args[@]}" || die "Inicjalizacja klastra PostgreSQL ${TARGET_VERSION} nie powiodła się."
}
migrate_postgresql_conf() {
local src="$1"
local dst="$2"
local tmp_file
tmp_file="$(mktemp)"
cp -p "$dst" "${dst}.preupgrade.$(date +%s)"
{
cat "$dst"
echo ""
echo "# --- Migrated settings from PostgreSQL ${CURRENT_VERSION} on $(date) ---"
awk '
function trim(s) { sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s); return s }
{
line = $0
t = trim(line)
if (t == "" || t ~ /^#/) next
if (t ~ /^include([[:space:]]|_if_exists|_dir)/) { print line; next }
if (index(t, "=") == 0) next
split(t, a, "=")
key = tolower(trim(a[1]))
if (key ~ /^(data_directory|hba_file|ident_file|external_pid_file|config_file)$/) next
print line
}
' "$src"
echo "# --- End migrated settings ---"
} > "$tmp_file"
mv "$tmp_file" "$dst"
chown postgres:postgres "$dst"
chmod 600 "$dst"
}
copy_hba_conf() {
cp -p "$NEW_HBA" "${NEW_HBA}.preupgrade.$(date +%s)"
cp -p "$OLD_HBA" "$NEW_HBA"
chown postgres:postgres "$NEW_HBA"
chmod 600 "$NEW_HBA"
}
stop_clusters() {
sekcja "Zatrzymywanie usług PostgreSQL"
if systemctl is-active --quiet "$OLD_SERVICE"; then
systemctl stop "$OLD_SERVICE" || die "Nie udało się zatrzymać ${OLD_SERVICE}."
else
info "Usługa ${OLD_SERVICE} już jest zatrzymana."
fi
systemctl stop "$NEW_SERVICE" >/dev/null 2>&1 || true
}
run_pg_upgrade() {
sekcja "Aktualizacja danych przez pg_upgrade"
local jobs
jobs="$(nproc 2>/dev/null || echo 2)"
[[ "$jobs" =~ ^[0-9]+$ ]] || jobs=2
(( jobs >= 1 )) || jobs=1
local workdir="/var/lib/pgsql/upgrade_${CURRENT_VERSION}_to_${TARGET_VERSION}_$(date +%Y%m%d_%H%M%S)"
PG_UPGRADE_WORKDIR="$workdir"
mkdir -p "$workdir"
chown postgres:postgres "$workdir"
chmod 700 "$workdir"
sudo -u postgres env \
PGUSER="$PGUSER_VAL" \
PGPASSWORD="$PGPASSWORD_VAL" \
bash -c "cd '$workdir' && '$NEW_BINDIR/pg_upgrade' -U '$PGUSER_VAL' -b '$OLD_BINDIR' -B '$NEW_BINDIR' -d '$OLD_DATADIR' -D '$NEW_DATADIR' -j '$jobs'" \
|| die "pg_upgrade zakończył się błędem. Sprawdź logi w: ${workdir}"
if [[ -x "${workdir}/delete_old_cluster.sh" ]]; then
warn "Skrypt do usunięcia starego klastra dostępny: ${workdir}/delete_old_cluster.sh"
DELETE_OLD_CLUSTER_CMD="sudo -u postgres '${workdir}/delete_old_cluster.sh'"
fi
}
restore_logical_backup_to_new_cluster() {
sekcja "Odtwarzanie backupu logicznego w nowym klastrze"
[[ -n "$BACKUP_CLUSTER_FILE" ]] || die "Brak pliku pełnego dumpa do odtworzenia."
[[ -f "$BACKUP_CLUSTER_FILE" ]] || die "Plik dumpa nie istnieje: ${BACKUP_CLUSTER_FILE}"
local psql_bin="${NEW_BINDIR}/psql"
[[ -x "$psql_bin" ]] || die "Brak narzędzia psql dla nowej wersji: ${psql_bin}"
[[ -n "$RESTORE_MAINT_DB" ]] || die "Brak nazwy bazy serwisowej restore."
local sanitize_restore_awk
sanitize_restore_awk='
function lower(s) { return tolower(s) }
{
line = $0
l = lower(line)
if (l ~ /^[[:space:]]*drop[[:space:]]+role[[:space:]]+if[[:space:]]+exists[[:space:]]+"?postgres"?[[:space:]]*;[[:space:]]*$/) next
if (l ~ /^[[:space:]]*create[[:space:]]+role[[:space:]]+"?postgres"?([[:space:]]|;)/) next
if (l ~ /^[[:space:]]*comment[[:space:]]+on[[:space:]]+role[[:space:]]+"?postgres"?([[:space:]]|;)/) next
print line
}
'
info "Przywracam pełny dump klastra z pliku: ${BACKUP_CLUSTER_FILE}"
gzip -dc "$BACKUP_CLUSTER_FILE" \
| awk "$sanitize_restore_awk" \
| sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d "$RESTORE_MAINT_DB" \
|| die "Odtwarzanie dumpa do nowego klastra nie powiodło się."
}
cleanup_restore_maintenance_database() {
[[ "$UPGRADE_MODE" == "backup_restore" ]] || return 0
[[ -n "$RESTORE_MAINT_DB" ]] || return 0
[[ "$RESTORE_MAINT_DB" =~ ^da_restore_maint_[0-9]{14}$ ]] || {
warn "Pomijam usuwanie bazy serwisowej o nieoczekiwanej nazwie: ${RESTORE_MAINT_DB}"
return 0
}
sekcja "Usuwanie tymczasowej bazy serwisowej restore"
local psql_bin="${NEW_BINDIR}/psql"
[[ -x "$psql_bin" ]] || die "Brak narzędzia psql dla nowej wersji: ${psql_bin}"
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "REVOKE CONNECT ON DATABASE \"${RESTORE_MAINT_DB}\" FROM PUBLIC;" \
|| true
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${RESTORE_MAINT_DB}' AND pid <> pg_backend_pid();" \
|| true
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "DROP DATABASE IF EXISTS \"${RESTORE_MAINT_DB}\";" \
|| die "Nie udało się usunąć tymczasowej bazy serwisowej: ${RESTORE_MAINT_DB}"
info "Usunięto tymczasową bazę serwisową restore: ${RESTORE_MAINT_DB}"
}
cleanup_temporary_bootstrap_superuser() {
[[ "$UPGRADE_MODE" == "backup_restore" ]] || return 0
[[ "$INITDB_SUPERUSER" != "postgres" ]] || return 0
[[ "$INITDB_SUPERUSER" =~ ^da_upgrade_bootstrap_[0-9]{14}$ ]] || {
warn "Pomijam usuwanie superusera bootstrap o nieoczekiwanej nazwie: ${INITDB_SUPERUSER}"
return 0
}
sekcja "Usuwanie tymczasowego superusera bootstrap"
local psql_bin="${NEW_BINDIR}/psql"
[[ -x "$psql_bin" ]] || die "Brak narzędzia psql dla nowej wersji: ${psql_bin}"
local keep_role=""
keep_role="$(
sudo -u postgres "$psql_bin" -X -At -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT rolname FROM pg_roles WHERE rolsuper AND rolcanlogin AND rolname <> '${INITDB_SUPERUSER}' ORDER BY (rolname='postgres') DESC, rolname LIMIT 1;"
)"
[[ -n "$keep_role" ]] || die "Brak dostępnej roli superuser (login) do przejęcia własności po ${INITDB_SUPERUSER}."
[[ "$keep_role" =~ ^[A-Za-z0-9_]{1,63}$ ]] || die "Nieprawidłowa rola docelowa przejęcia własności: ${keep_role}"
info "Rola przejmująca własność: ${keep_role}"
mapfile -t all_dbs < <(
sudo -u postgres "$psql_bin" -X -At -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT datname FROM pg_database WHERE datallowconn AND datname <> 'template0' ORDER BY datname;"
)
local db
for db in "${all_dbs[@]}"; do
[[ -n "$db" ]] || continue
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d "$db" \
-c "REASSIGN OWNED BY \"${INITDB_SUPERUSER}\" TO \"${keep_role}\";" \
|| die "Nie udało się wykonać REASSIGN OWNED w bazie ${db}."
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d "$db" \
-c "DROP OWNED BY \"${INITDB_SUPERUSER}\";" \
|| die "Nie udało się wykonać DROP OWNED w bazie ${db}."
done
mapfile -t owned_dbs < <(
sudo -u postgres "$psql_bin" -X -At -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT datname FROM pg_database WHERE datdba = (SELECT oid FROM pg_roles WHERE rolname = '${INITDB_SUPERUSER}') AND datname <> 'template0' ORDER BY datname;"
)
for db in "${owned_dbs[@]}"; do
[[ -n "$db" ]] || continue
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "ALTER DATABASE \"${db}\" OWNER TO \"${keep_role}\";" \
|| die "Nie udało się zmienić właściciela bazy ${db}."
done
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$keep_role" -d template1 \
-c "DROP ROLE \"${INITDB_SUPERUSER}\";" \
|| die "Nie udało się usunąć tymczasowego superusera: ${INITDB_SUPERUSER}."
info "Usunięto tymczasowego superusera: ${INITDB_SUPERUSER}"
}
sync_admin_role_password_after_restore() {
[[ "$UPGRADE_MODE" == "backup_restore" ]] || return 0
sekcja "Synchronizacja hasła roli administracyjnej po restore"
local psql_bin="${NEW_BINDIR}/psql"
[[ -x "$psql_bin" ]] || die "Brak narzędzia psql dla nowej wersji: ${psql_bin}"
[[ -n "$PGUSER_VAL" ]] || die "Brak roli administracyjnej PostgreSQL (PG_USER)."
[[ "$PGUSER_VAL" =~ ^[A-Za-z0-9_@.-]{1,63}$ ]] || die "Nieprawidłowa nazwa roli administracyjnej: ${PGUSER_VAL}"
local role_name_sql role_password_sql
role_name_sql="$(printf "%s" "$PGUSER_VAL" | sed "s/'/''/g")"
role_password_sql="$(printf "%s" "$PGPASSWORD_VAL" | sed "s/'/''/g")"
local role_exists=""
role_exists="$(
sudo -u postgres "$psql_bin" -X -At -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT 1 FROM pg_roles WHERE rolname = '${role_name_sql}' LIMIT 1;"
)"
[[ "$role_exists" == "1" ]] || die "Rola administracyjna '${PGUSER_VAL}' nie istnieje po restore."
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "DO \$\$ BEGIN EXECUTE format('ALTER ROLE %I WITH LOGIN PASSWORD %L', '${role_name_sql}', '${role_password_sql}'); END \$\$;" \
|| die "Nie udało się zsynchronizować hasła roli '${PGUSER_VAL}' po restore."
info "Zsynchronizowano hasło roli ${PGUSER_VAL} z danymi z ${CRED_FILE}."
}
prepare_fresh_cluster_for_full_restore() {
sekcja "Przygotowanie świeżego klastra do pełnego restore"
local psql_bin="${NEW_BINDIR}/psql"
[[ -x "$psql_bin" ]] || die "Brak narzędzia psql dla nowej wersji: ${psql_bin}"
[[ -n "$RESTORE_MAINT_DB" ]] || die "Brak nazwy tymczasowej bazy serwisowej do restore."
[[ "$RESTORE_MAINT_DB" =~ ^[a-z0-9_]{1,63}$ ]] || die "Nieprawidłowa nazwa bazy serwisowej restore: ${RESTORE_MAINT_DB}"
# W trybie backup+restore dump jest tworzony bez wykluczania baz
# (z opcjami --clean --if-exists), więc nie usuwamy ręcznie bazy postgres.
info "Weryfikuję dostęp do świeżego klastra przed odtworzeniem dumpa."
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "SELECT 1;" \
|| die "Nie udało się przygotować nowego klastra do restore."
info "Tworzę tymczasową bazę serwisową restore: ${RESTORE_MAINT_DB}"
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "DROP DATABASE IF EXISTS \"${RESTORE_MAINT_DB}\";" \
|| die "Nie udało się usunąć poprzedniej bazy serwisowej restore."
sudo -u postgres "$psql_bin" -X -v ON_ERROR_STOP=1 -U "$INITDB_SUPERUSER" -d template1 \
-c "CREATE DATABASE \"${RESTORE_MAINT_DB}\";" \
|| die "Nie udało się utworzyć bazy serwisowej restore."
}
restart_new_service_after_config_migration() {
sekcja "Restart usługi PostgreSQL po migracji konfiguracji"
systemctl restart "$NEW_SERVICE" || die "Nie udało się zrestartować usługi ${NEW_SERVICE}."
systemctl is-active --quiet "$NEW_SERVICE" || die "Usługa ${NEW_SERVICE} nie działa po restarcie."
info "Usługa ${NEW_SERVICE} została zrestartowana po migracji konfiguracji."
}
run_post_upgrade_maintenance() {
sekcja "Statystyki po aktualizacji (vacuumdb)"
local analyze_script=""
if [[ -n "$PG_UPGRADE_WORKDIR" ]]; then
analyze_script="${PG_UPGRADE_WORKDIR}/analyze_new_cluster.sh"
fi
if [[ -n "$analyze_script" && -x "$analyze_script" ]]; then
info "Uruchamiam ${analyze_script}."
if sudo -u postgres "$analyze_script"; then
return 0
fi
warn "analyze_new_cluster.sh zakończył się błędem. Uruchamiam fallback vacuumdb."
fi
local vacuumdb_bin="${NEW_BINDIR}/vacuumdb"
if [[ ! -x "$vacuumdb_bin" ]]; then
warn "Brak narzędzia vacuumdb (${vacuumdb_bin}). Pomijam aktualizację statystyk."
return 0
fi
info "Uruchamiam: vacuumdb --all --analyze-in-stages --missing-stats-only"
sudo -u postgres env \
PGHOST="$PGHOST_VAL" \
PGPORT="$PGPORT_VAL" \
PGUSER="$PGUSER_VAL" \
PGPASSWORD="$PGPASSWORD_VAL" \
"$vacuumdb_bin" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$PGUSER_VAL" --all --analyze-in-stages --missing-stats-only \
|| warn "Pierwszy etap vacuumdb zakończył się błędem."
info "Uruchamiam: vacuumdb --all --analyze-only"
sudo -u postgres env \
PGHOST="$PGHOST_VAL" \
PGPORT="$PGPORT_VAL" \
PGUSER="$PGUSER_VAL" \
PGPASSWORD="$PGPASSWORD_VAL" \
"$vacuumdb_bin" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$PGUSER_VAL" --all --analyze-only \
|| warn "Drugi etap vacuumdb zakończył się błędem."
}
generate_remove_old_postgres_script() {
sekcja "Generowanie skryptu usuwania starej wersji PostgreSQL"
local delete_script=""
if [[ -n "$PG_UPGRADE_WORKDIR" ]]; then
delete_script="${PG_UPGRADE_WORKDIR}/delete_old_cluster.sh"
fi
cat > "$REMOVE_OLD_SCRIPT_PATH" <<EOF
#!/bin/bash
set -euo pipefail
OLD_VERSION="${CURRENT_VERSION}"
OLD_SERVICE="${OLD_SERVICE}"
NEW_SERVICE="${NEW_SERVICE}"
OLD_DATADIR="${OLD_DATADIR}"
DELETE_OLD_CLUSTER_SCRIPT="${delete_script}"
if [[ \$EUID -ne 0 ]]; then
echo "[BŁĄD] Skrypt musi być uruchomiony jako root." >&2
exit 1
fi
echo "Stara wersja PostgreSQL do usunięcia: \${OLD_VERSION}"
read -r -p "Czy na pewno usunąć starą wersję PostgreSQL \${OLD_VERSION} (dane i pakiety)? [t/N]: " answer
case "\${answer,,}" in
t|tak|y|yes) ;;
*) echo "[INFO] Anulowano usuwanie starej wersji."; exit 0 ;;
esac
if ! systemctl is-active --quiet "\${NEW_SERVICE}"; then
echo "[BŁĄD] Nowa usługa \${NEW_SERVICE} nie działa. Przerywam." >&2
exit 1
fi
if [[ -n "\${DELETE_OLD_CLUSTER_SCRIPT}" && -x "\${DELETE_OLD_CLUSTER_SCRIPT}" ]]; then
echo "[INFO] Uruchamiam: \${DELETE_OLD_CLUSTER_SCRIPT}"
sudo -u postgres "\${DELETE_OLD_CLUSTER_SCRIPT}"
else
echo "[INFO] Usuwam katalog danych starego klastra: \${OLD_DATADIR}"
systemctl stop "\${OLD_SERVICE}" >/dev/null 2>&1 || true
if [[ -d "\${OLD_DATADIR}" ]]; then
rm -rf "\${OLD_DATADIR}"
else
echo "[WARN] Katalog danych nie istnieje: \${OLD_DATADIR}"
fi
fi
systemctl disable "\${OLD_SERVICE}" >/dev/null 2>&1 || true
mapfile -t old_pkgs < <(rpm -qa --qf '%{NAME}\n' | grep -E "^postgresql\${OLD_VERSION}($|-)" | sort -u || true)
if [[ \${#old_pkgs[@]} -gt 0 ]]; then
echo "[INFO] Usuwam pakiety starej wersji: \${old_pkgs[*]}"
dnf remove -y "\${old_pkgs[@]}"
else
echo "[WARN] Brak pakietów PostgreSQL \${OLD_VERSION} do usunięcia."
fi
echo "[INFO] Zakończono usuwanie starej wersji PostgreSQL \${OLD_VERSION}."
EOF
chmod 700 "$REMOVE_OLD_SCRIPT_PATH"
chown root:root "$REMOVE_OLD_SCRIPT_PATH" || true
REMOVE_OLD_SCRIPT_RESULT="wygenerowano: ${REMOVE_OLD_SCRIPT_PATH}"
DELETE_OLD_CLUSTER_CMD="$REMOVE_OLD_SCRIPT_PATH"
info "Wygenerowano skrypt: ${REMOVE_OLD_SCRIPT_PATH} (uprawnienia 700)."
}
start_new_service() {
sekcja "Uruchamianie nowej usługi PostgreSQL"
systemctl enable "$NEW_SERVICE" || die "Nie udało się włączyć usługi ${NEW_SERVICE}."
systemctl start "$NEW_SERVICE" || die "Nie udało się uruchomić usługi ${NEW_SERVICE}."
systemctl is-active --quiet "$NEW_SERVICE" || die "Usługa ${NEW_SERVICE} nie jest aktywna po uruchomieniu."
systemctl disable "$OLD_SERVICE" >/dev/null 2>&1 || true
info "Usługa ${NEW_SERVICE} działa."
}
update_pg_config_symlink() {
local src="${NEW_BINDIR}/pg_config"
local dst="/usr/local/bin/pg_config"
if [[ -x "$src" ]]; then
ln -sf "$src" "$dst"
info "Zaktualizowano symlink: ${dst} -> ${src}"
fi
cb_pgsql_configure_runtime_libpq "$NEW_PREFIX"
}
update_php_config_and_build() {
[[ "$PHP_UPDATE_NEEDED" == "tak" ]] || return 0
[[ -x "${CB_DIR}/build" ]] || { warn "Brak ${CB_DIR}/build. Pomijam aktualizację PHP."; return 0; }
sekcja "Aktualizacja ścieżek PostgreSQL w configure.phpXX i rekompilacja PHP"
local ts
ts="$(date +%s)"
local f
local version
for f in "${PHP_FILES_TO_UPDATE[@]}"; do
cp -p "$f" "${f}.bak.${ts}"
sed -i "s|${OLD_PREFIX}|${NEW_PREFIX}|g" "$f"
info "Zaktualizowano: $f"
done
cd "$CB_DIR"
if [[ ${#PHP_VERSIONS_TO_REBUILD[@]} -eq 0 ]]; then
LD_LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" \
LD_PRELOAD="${DA_PG_RUNTIME_LIBPQ}${LD_PRELOAD:+:${LD_PRELOAD}}" \
LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LIBRARY_PATH:+:${LIBRARY_PATH}}" \
PKG_CONFIG_PATH="${DA_PG_RUNTIME_LIBDIR}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" \
LDFLAGS="-L${DA_PG_RUNTIME_LIBDIR} ${LDFLAGS:-}" \
CPPFLAGS="-I${NEW_PREFIX}/include ${CPPFLAGS:-}" \
./build php || die "Rekompilacja PHP nie powiodła się."
else
mapfile -t PHP_VERSIONS_TO_REBUILD < <(printf '%s\n' "${PHP_VERSIONS_TO_REBUILD[@]}" | sort -u)
for version in "${PHP_VERSIONS_TO_REBUILD[@]}"; do
LD_LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" \
LD_PRELOAD="${DA_PG_RUNTIME_LIBPQ}${LD_PRELOAD:+:${LD_PRELOAD}}" \
LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LIBRARY_PATH:+:${LIBRARY_PATH}}" \
PKG_CONFIG_PATH="${DA_PG_RUNTIME_LIBDIR}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" \
LDFLAGS="-L${DA_PG_RUNTIME_LIBDIR} ${LDFLAGS:-}" \
CPPFLAGS="-I${NEW_PREFIX}/include ${CPPFLAGS:-}" \
./build php "$version" || die "Rekompilacja PHP ${version} nie powiodła się."
done
fi
./build rewrite_confs || warn "rewrite_confs zakończył się błędem."
cd - >/dev/null
}
final_summary() {
sekcja "Aktualizacja PostgreSQL zakończona"
printf "Stara wersja PostgreSQL:\t%s\n" "$CURRENT_VERSION"
printf "Nowa wersja PostgreSQL:\t%s\n" "$TARGET_VERSION"
printf "Tryb aktualizacji:\t%s\n" "$UPGRADE_MODE"
printf "Nowy katalog danych:\t%s\n" "$NEW_DATADIR"
printf "Backup wykonany:\t%s\n" "$DO_BACKUP"
if [[ "$DO_BACKUP" == "tak" ]]; then
printf "Rodzaj backupu:\t%s\n" "$BACKUP_RESULT"
fi
if [[ -n "$BACKUP_DIR" ]]; then
printf "Katalog backupu:\t%s\n" "$BACKUP_DIR"
fi
if [[ -n "$DELETE_OLD_CLUSTER_CMD" ]]; then
printf "Polecenie usunięcia starego klastra:\t%s\n" "$DELETE_OLD_CLUSTER_CMD"
fi
printf "Skrypt usuwania starej wersji:\t%s\n" "$REMOVE_OLD_SCRIPT_RESULT"
printf "Aktualizacja PHP:\t%s\n" "$PHP_UPDATE_NEEDED"
echo ""
info "Pełny log: ${LOGFILE}"
}
main() {
detect_os
detect_installed_versions
detect_current_version
choose_target_version
choose_upgrade_mode
prepare_paths
load_pg_credentials
if [[ "$UPGRADE_MODE" == "backup_restore" ]]; then
DO_BACKUP="tak"
info "Tryb backup+restore wymaga wykonania backupu logicznego. Ustawiono: backup=tak."
else
if ask_yes_no "Wykonać pełny backup wszystkich baz (z rolami/użytkownikami) przed aktualizacją?" "t"; then
DO_BACKUP="tak"
else
DO_BACKUP="nie"
fi
fi
detect_php_update_need
print_summary_and_confirm
ensure_sudo_available
detect_old_cluster_checksum_mode
# Bezpieczeństwo: najpierw pełny backup, dopiero potem działania modyfikujące.
run_full_backup
if [[ "$UPGRADE_MODE" == "pg_upgrade" ]]; then
install_target_packages
init_new_cluster
stop_clusters
run_pg_upgrade
sekcja "Migracja ustawień postgresql.conf i pg_hba.conf"
migrate_postgresql_conf "$OLD_CONF" "$NEW_CONF"
copy_hba_conf
start_new_service
else
install_target_packages
init_new_cluster
stop_clusters
start_new_service
prepare_fresh_cluster_for_full_restore
restore_logical_backup_to_new_cluster
cleanup_restore_maintenance_database
cleanup_temporary_bootstrap_superuser
sync_admin_role_password_after_restore
sekcja "Migracja ustawień postgresql.conf i pg_hba.conf"
migrate_postgresql_conf "$OLD_CONF" "$NEW_CONF"
copy_hba_conf
restart_new_service_after_config_migration
DELETE_OLD_CLUSTER_CMD="systemctl stop ${OLD_SERVICE} && rm -rf '${OLD_DATADIR}'"
fi
if [[ -z "$DELETE_OLD_CLUSTER_CMD" ]]; then
DELETE_OLD_CLUSTER_CMD="systemctl stop ${OLD_SERVICE} && rm -rf '${OLD_DATADIR}'"
fi
run_post_upgrade_maintenance
generate_remove_old_postgres_script
update_pg_config_symlink
update_php_config_and_build
final_summary
}
main "$@"