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.
This commit is contained in:
MHSanaei
2026-07-06 09:24:18 +02:00
parent de70ecb026
commit f36f481e02
3 changed files with 112 additions and 4 deletions
+1 -1
View File
@@ -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)
}
@@ -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",