1
0
Эх сурвалжийг харах

feat(db): add pgclient command to install or upgrade PostgreSQL client tools

Restoring a panel backup made by a newer pg_dump fails when the host's
pg_restore is older, and the existing pg_ensure_client only installs the
distribution package when the tools are missing - it can never upgrade,
and distribution repositories often cap below the required major.

Add pg_upgrade_client to x-ui.sh, exposed as 'x-ui pgclient [major]' and
as a PostgreSQL menu entry: it checks the installed pg_restore major,
tries the distribution package for the exact requested major first, and
falls back to the official PostgreSQL repository (apt on Debian/Ubuntu,
yum/dnf on Enterprise Linux, with a /usr/pgsql PATH symlink fallback);
Arch, Alpine and openSUSE install their current package. The panel's
dump-version mismatch error now names the ready-to-copy command with
the exact major parsed from the dump header.
MHSanaei 1 өдөр өмнө
parent
commit
f36f481e02

+ 1 - 1
internal/web/service/server.go

@@ -1640,7 +1640,7 @@ func pgRestoreReadFailureError(probeOutput, localVersion string) error {
 		localVersion = "unknown"
 	}
 	if major, known := pgArchiveVersionIntroducedIn[m[1]]; known {
-		return common.NewErrorf("This backup was created by pg_dump from PostgreSQL %d or newer, but the server's pg_restore is version %s and cannot read it; upgrade the postgresql-client package to version %d or newer and retry the import", major, localVersion, major)
+		return common.NewErrorf("This backup was created by pg_dump from PostgreSQL %d or newer, but the server's pg_restore is version %s and cannot read it; run 'x-ui pgclient %d' on the server (or upgrade the postgresql-client package to version %d or newer), then retry the import", major, localVersion, major, major)
 	}
 	return common.NewErrorf("This backup was created by a newer pg_dump than the server's pg_restore (version %s) can read; upgrade the postgresql-client package and retry the import", localVersion)
 }

+ 3 - 3
internal/web/service/server_pg_restore_test.go

@@ -13,13 +13,13 @@ func TestPgRestoreReadFailureError(t *testing.T) {
 			name:         "dump from postgres 17 on older client",
 			probeOutput:  "pg_restore: error: unsupported version (1.16) in file header",
 			localVersion: "16.4",
-			want:         "This backup was created by pg_dump from PostgreSQL 17 or newer, but the server's pg_restore is version 16.4 and cannot read it; upgrade the postgresql-client package to version 17 or newer and retry the import",
+			want:         "This backup was created by pg_dump from PostgreSQL 17 or newer, but the server's pg_restore is version 16.4 and cannot read it; run 'x-ui pgclient 17' on the server (or upgrade the postgresql-client package to version 17 or newer), then retry the import",
 		},
 		{
 			name:         "dump from postgres 16 on older client",
 			probeOutput:  "pg_restore: error: unsupported version (1.15) in file header",
 			localVersion: "15.8",
-			want:         "This backup was created by pg_dump from PostgreSQL 16 or newer, but the server's pg_restore is version 15.8 and cannot read it; upgrade the postgresql-client package to version 16 or newer and retry the import",
+			want:         "This backup was created by pg_dump from PostgreSQL 16 or newer, but the server's pg_restore is version 15.8 and cannot read it; run 'x-ui pgclient 16' on the server (or upgrade the postgresql-client package to version 16 or newer), then retry the import",
 		},
 		{
 			name:         "archive version newer than any known mapping",
@@ -31,7 +31,7 @@ func TestPgRestoreReadFailureError(t *testing.T) {
 			name:         "local version could not be determined",
 			probeOutput:  "pg_restore: error: unsupported version (1.16) in file header",
 			localVersion: "",
-			want:         "This backup was created by pg_dump from PostgreSQL 17 or newer, but the server's pg_restore is version unknown and cannot read it; upgrade the postgresql-client package to version 17 or newer and retry the import",
+			want:         "This backup was created by pg_dump from PostgreSQL 17 or newer, but the server's pg_restore is version unknown and cannot read it; run 'x-ui pgclient 17' on the server (or upgrade the postgresql-client package to version 17 or newer), then retry the import",
 		},
 		{
 			name:         "unrelated read failure passes through",

+ 108 - 0
x-ui.sh

@@ -3012,6 +3012,104 @@ pg_ensure_client() {
     command -v pg_dump > /dev/null 2>&1 && command -v pg_restore > /dev/null 2>&1
 }
 
+# Prints the major version of the installed pg_restore, or nothing when absent.
+pg_client_major() {
+    command -v pg_restore > /dev/null 2>&1 || return 1
+    pg_restore --version 2> /dev/null | grep -oE '[0-9]+' | head -n 1
+}
+
+# Installs or upgrades the PostgreSQL client tools (pg_dump/pg_restore) so their
+# major version is at least $1 (e.g. 17); with no argument any installed version
+# is accepted. Falls back to the official PostgreSQL package repository when the
+# distribution one is too old. Restoring a panel backup made by a newer pg_dump
+# needs this:   x-ui pgclient <major>
+pg_upgrade_client() {
+    local want="$1" have
+    if [[ -n "$want" && ! "$want" =~ ^[0-9]+$ ]]; then
+        LOGE "Invalid PostgreSQL major version '${want}' (expected a number like 17)."
+        return 1
+    fi
+    have=$(pg_client_major)
+    if [[ -n "$have" ]]; then
+        if [[ -z "$want" || "$have" -ge "$want" ]]; then
+            LOGI "PostgreSQL client tools are already installed (version ${have})."
+            return 0
+        fi
+        LOGI "Installed PostgreSQL client tools are version ${have}; version ${want} or newer is required."
+    fi
+    if [[ "${running_in_docker}" == "true" ]]; then
+        LOGI "Note: packages installed inside the container are lost when the container is recreated."
+    fi
+    case "${release}" in
+        ubuntu | debian | armbian)
+            apt-get update >&2 || return 1
+            if [[ -z "$want" ]]; then
+                apt-get install -y -q postgresql-client >&2 || return 1
+            elif ! apt-get install -y -q "postgresql-client-${want}" >&2; then
+                LOGI "postgresql-client-${want} is not in the distribution repositories; adding the official PostgreSQL apt repository..."
+                apt-get install -y -q postgresql-common ca-certificates >&2 || return 1
+                /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y >&2 || return 1
+                apt-get install -y -q "postgresql-client-${want}" >&2 || return 1
+            fi
+            ;;
+        fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol | centos)
+            local pkg_mgr="dnf"
+            command -v dnf > /dev/null 2>&1 || pkg_mgr="yum"
+            if [[ -z "$want" ]]; then
+                "$pkg_mgr" install -y -q postgresql >&2 || return 1
+            elif ! "$pkg_mgr" install -y -q "postgresql${want}" >&2; then
+                local elver
+                elver=$(rpm -E %rhel 2> /dev/null)
+                if [[ ! "$elver" =~ ^[0-9]+$ ]]; then
+                    LOGE "Could not determine the Enterprise Linux release; install the PostgreSQL ${want} client tools manually."
+                    return 1
+                fi
+                LOGI "postgresql${want} is not in the enabled repositories; adding the official PostgreSQL yum repository..."
+                "$pkg_mgr" install -y "https://download.postgresql.org/pub/repos/yum/reporpms/EL-${elver}-$(uname -m)/pgdg-redhat-repo-latest.noarch.rpm" >&2 || return 1
+                if [[ "$pkg_mgr" == "dnf" ]]; then
+                    dnf -qy module disable postgresql >&2 || true
+                fi
+                "$pkg_mgr" install -y -q "postgresql${want}" >&2 || return 1
+            fi
+            ;;
+        arch | manjaro | parch)
+            pacman -Sy --noconfirm postgresql >&2 || return 1
+            ;;
+        opensuse-tumbleweed | opensuse-leap)
+            if [[ -z "$want" ]] || ! zypper -q install -y "postgresql${want}" >&2; then
+                zypper -q install -y postgresql >&2 || return 1
+            fi
+            ;;
+        alpine)
+            if [[ -z "$want" ]] || ! apk add --no-cache "postgresql${want}-client" >&2; then
+                apk add --no-cache postgresql-client >&2 || return 1
+            fi
+            ;;
+        *)
+            LOGE "Unsupported OS '${release}'; install the PostgreSQL client tools manually."
+            return 1
+            ;;
+    esac
+    hash -r 2> /dev/null
+    have=$(pg_client_major)
+    if [[ -n "$want" && ( -z "$have" || "$have" -lt "$want" ) && -x "/usr/pgsql-${want}/bin/pg_restore" ]]; then
+        ln -sf "/usr/pgsql-${want}/bin/pg_dump" /usr/local/bin/pg_dump
+        ln -sf "/usr/pgsql-${want}/bin/pg_restore" /usr/local/bin/pg_restore
+        hash -r 2> /dev/null
+        have=$(pg_client_major)
+    fi
+    if [[ -z "$have" ]]; then
+        LOGE "pg_dump/pg_restore are still unavailable after installation."
+        return 1
+    fi
+    if [[ -n "$want" && "$have" -lt "$want" ]]; then
+        LOGE "PostgreSQL client tools are version ${have} after installation but ${want} or newer is required; install them manually."
+        return 1
+    fi
+    LOGI "PostgreSQL client tools are ready (version ${have})."
+    return 0
+}
+
 # Writes XUI_DB_TYPE/XUI_DB_DSN into the service env file, preserving other entries.
 pg_write_env() {
     local dsn="$1" envfile
@@ -3122,6 +3220,7 @@ postgresql_menu() {
     echo -e "${green}\t7.${plain} ${green}Enable${plain} Autostart on boot"
     echo -e "${green}\t8.${plain} View PostgreSQL Log"
     echo -e "${green}\t9.${plain} Convert SQLite ${green}.db <-> .dump${plain}"
+    echo -e "${green}\t10.${plain} Install/Upgrade client tools (pg_dump/pg_restore)"
     echo -e "${green}\t0.${plain} Back to Main Menu"
     read -rp "Choose an option: " choice
     case "$choice" in
@@ -3164,6 +3263,11 @@ postgresql_menu() {
             migrate_db_prompt
             postgresql_menu
             ;;
+        10)
+            read -rp "Required PostgreSQL major version (empty = any): " pg_client_ver
+            pg_upgrade_client "$pg_client_ver"
+            postgresql_menu
+            ;;
         *)
             echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
             postgresql_menu
@@ -3283,6 +3387,7 @@ show_usage() {
 │  ${blue}x-ui update-dev${plain}            - Update to Dev channel (latest)   │
 │  ${blue}x-ui update-all-geofiles${plain}   - Update all geo files             │
 │  ${blue}x-ui migrateDB [file]${plain}      - Convert .db <-> .dump (SQLite)   │
+│  ${blue}x-ui pgclient [ver]${plain}        - Upgrade pg_dump/pg_restore tools │
 │  ${blue}x-ui legacy${plain}                - Legacy version                   │
 │  ${blue}x-ui install${plain}               - Install                          │
 │  ${blue}x-ui uninstall${plain}             - Uninstall                        │
@@ -3486,6 +3591,9 @@ if [[ $# > 0 ]]; then
         "migrateDB")
             migrate_db "$2" "$3"
             ;;
+        "pgclient")
+            pg_upgrade_client "$2"
+            ;;
         *) show_usage ;;
     esac
 else