#!/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" <&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 "$@"