mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-17 04:17:21 +00:00
* fix(backup): detect pg server major for version-aware pg_dump hints pg_dump aborts when its major version is older than the server's, so backup preflight now runs SHOW server_version_num against the live PG server and uses the detected major to drive: - the missing-pg_dump hint (names the exact postgresqlNN-client) - a new compat check that flags an installed-but-too-old pg_dump as not-ready, instead of letting backup fail partway through the dump Adds ParsePgDumpMajor helper for Debian/Homebrew/EDB output shapes, covered by table-driven unit tests. Normalizes nil ctx once at RunPreflight boundary so downstream exec.CommandContext calls are safe. Stubs detectPGServerMajor + checkPgDumpServerCompat for the sqliteonly build. UI: removes the duplicate static hint from the preflight alert box so the dynamic, actionable warning is the single source of truth. Drops the obsolete pgDumpHint i18n key from en/vi/zh locale files. Refs #829 * fix(docker): bump runtime base alpine 3.22 → 3.23 Alpine 3.23 main ships postgresql16-client, postgresql17-client, and postgresql18-client simultaneously. Alpine 3.22 only shipped up to postgresql17-client, so the backup preflight's dynamic hint to install postgresql18-client previously failed with "no such package" on PG 18 deployments. No client package is pre-bundled: the on-demand install via the Packages page now resolves for any supported PG major. Refs #829
115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
//go:build !sqliteonly
|
|
|
|
package backup
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
// detectPGServerMajor queries the live PostgreSQL server for its major
|
|
// version number. Returns 0 on any error (no DSN, unreachable, parse
|
|
// failure) or for exotic values below the PG 8.x floor, so callers can
|
|
// fall back to a generic hint. Uses server_version_num which encodes
|
|
// major*10000 + minor for PG 10+ (e.g. 180003 = 18.3, 170009 = 17.9).
|
|
func detectPGServerMajor(ctx context.Context, dsn string) int {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
defer db.Close()
|
|
|
|
var serverNum int
|
|
if err := db.QueryRowContext(ctx, "SHOW server_version_num").Scan(&serverNum); err != nil {
|
|
return 0
|
|
}
|
|
major := serverNum / 10000
|
|
// Clamp implausible values (negative, zero, or pre-8 output from an
|
|
// exotic pooler) so we never render a hint like "postgresql-1-client".
|
|
if major < 8 {
|
|
return 0
|
|
}
|
|
return major
|
|
}
|
|
|
|
// checkPgDumpServerCompat verifies that the installed pg_dump can dump a
|
|
// PostgreSQL server of the given major version. pg_dump's major version must
|
|
// be >= the server's; otherwise pg_dump aborts at runtime with a version
|
|
// mismatch error. Returns (check, compatible). When the check cannot read
|
|
// pg_dump's version, the result is a non-fatal warning and compatible=true.
|
|
func checkPgDumpServerCompat(ctx context.Context, serverMajor int) (PreflightCheck, bool) {
|
|
name := "pg_version_compat"
|
|
|
|
pgDumpVer, err := PgDumpVersion(ctx)
|
|
if err != nil {
|
|
return PreflightCheck{
|
|
Name: name,
|
|
Status: "warning",
|
|
Detail: fmt.Sprintf("could not read pg_dump version: %v", err),
|
|
}, true
|
|
}
|
|
clientMajor := ParsePgDumpMajor(pgDumpVer)
|
|
if clientMajor == 0 {
|
|
return PreflightCheck{
|
|
Name: name,
|
|
Status: "warning",
|
|
Detail: fmt.Sprintf("could not parse pg_dump version: %q", pgDumpVer),
|
|
}, true
|
|
}
|
|
|
|
if clientMajor < serverMajor {
|
|
return PreflightCheck{
|
|
Name: name,
|
|
Status: "missing",
|
|
Detail: fmt.Sprintf("pg_dump %d cannot dump PostgreSQL %d server (major version mismatch)", clientMajor, serverMajor),
|
|
Hint: fmt.Sprintf("Install postgresql%d-client to match the server major version %d.", serverMajor, serverMajor),
|
|
}, false
|
|
}
|
|
return PreflightCheck{
|
|
Name: name,
|
|
Status: "ok",
|
|
Detail: fmt.Sprintf("pg_dump %d, server %d", clientMajor, serverMajor),
|
|
}, true
|
|
}
|
|
|
|
func checkDBSize(ctx context.Context, dsn string) (PreflightCheck, int64) {
|
|
creds, err := ParseDSN(dsn)
|
|
if err != nil {
|
|
return PreflightCheck{
|
|
Name: "db_size",
|
|
Status: "warning",
|
|
Detail: "could not parse DSN to estimate database size",
|
|
}, 0
|
|
}
|
|
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
return PreflightCheck{
|
|
Name: "db_size",
|
|
Status: "warning",
|
|
Detail: fmt.Sprintf("could not open DB connection: %v", err),
|
|
}, 0
|
|
}
|
|
defer db.Close()
|
|
|
|
var sizeBytes int64
|
|
if err := db.QueryRowContext(ctx, "SELECT pg_database_size($1)", creds.DBName).Scan(&sizeBytes); err != nil {
|
|
return PreflightCheck{
|
|
Name: "db_size",
|
|
Status: "warning",
|
|
Detail: fmt.Sprintf("could not query database size: %v", err),
|
|
}, 0
|
|
}
|
|
return PreflightCheck{
|
|
Name: "db_size",
|
|
Status: "ok",
|
|
Detail: fmt.Sprintf("estimated %d MB", sizeBytes>>20),
|
|
}, sizeBytes
|
|
}
|