case 4690 - backup: run pg_dump without a shell, password in PGPASSWORD, failures detected by exit code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 20:00:52 -07:00
parent 6db7c12c49
commit 78f5863947
3 changed files with 78 additions and 21 deletions

View File

@@ -59,13 +59,13 @@ namespace AyaNova.Biz
// var DemandFileNamePrepend = OnDemand ? "manual-" : string.Empty;
//*************
//DO DATA BACKUP
//build command
//this is valid on windows
//C:\data\code\PostgreSQLPortable_12.0\App\PgSQL\bin\pg_dump --dbname=postgresql://postgres:raven@127.0.0.1:5432/AyaNova -Fc > huge_new.backup
//run pg_dump directly with no shell: each connection detail is its own argument and the password goes in PGPASSWORD,
//so a user name or password containing spaces or @ : / ? # % can't break the command (it used to be a postgresql:// URI
//with the raw password, run through cmd.exe or /bin/sh for a > file redirect), and the password is never on a command line
//equivalent to: pg_dump --host=localhost --port=5432 --username=postgres --dbname=AyaNova --format=custom --file=db-xxx.backup
// await JobsBiz.LogJobAsync(Guid.Empty, $"Data backup starting");
Npgsql.NpgsqlConnectionStringBuilder PostgresConnectionString = new Npgsql.NpgsqlConnectionStringBuilder(ServerBootConfig.AYANOVA_DB_CONNECTION);
var DBNameParameter = $"--dbname=postgresql://{PostgresConnectionString.Username}:{PostgresConnectionString.Password}@{PostgresConnectionString.Host}:{PostgresConnectionString.Port}/{PostgresConnectionString.Database}";
//var DataBackupFile = $"{DemandFileNamePrepend}db-{FileUtil.GetSafeDateFileName()}.backup";//presentation issue so don't use UTC for this one
var DataBackupFile = $"db-{FileUtil.GetSafeDateFileName()}.backup";//presentation issue so don't use UTC for this one
@@ -77,23 +77,24 @@ namespace AyaNova.Biz
if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_BACKUP_PG_DUMP_PATH))
BackupUtilityCommand = Path.Combine(ServerBootConfig.AYANOVA_BACKUP_PG_DUMP_PATH, BackupUtilityCommand);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
//put quotes around paths if spaces in them
if (BackupUtilityCommand.Contains(' '))
{
BackupUtilityCommand = $"\"{BackupUtilityCommand}\"";
}
var Arguments = new List<string>();
//host and user only when set, otherwise pg_dump's own defaults apply as they would for any omitted option
if (!string.IsNullOrWhiteSpace(PostgresConnectionString.Host))
Arguments.AddRange(new[] { "--host", PostgresConnectionString.Host });
Arguments.AddRange(new[] { "--port", PostgresConnectionString.Port.ToString() });
if (!string.IsNullOrWhiteSpace(PostgresConnectionString.Username))
Arguments.AddRange(new[] { "--username", PostgresConnectionString.Username });
Arguments.AddRange(new[] {
"--dbname", PostgresConnectionString.Database,
"--format=custom",
"--file", DataBackupFile,
"--no-password"//never prompt, a missing or wrong password must fail rather than hang
});
var PgEnvironment = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(PostgresConnectionString.Password))
PgEnvironment["PGPASSWORD"] = PostgresConnectionString.Password;
if (DataBackupFile.Contains(' '))
{
DataBackupFile = $"\"{DataBackupFile}\"";
}
}
var Arguments = $"{DBNameParameter} -Fc > {DataBackupFile}";
var Result = RunProgram.Run(BackupUtilityCommand, Arguments, log, MAXIMUM_MS_ALLOWED_FOR_PROCESSING);
var Result = RunProgram.RunNoShell(BackupUtilityCommand, Arguments, PgEnvironment, log, MAXIMUM_MS_ALLOWED_FOR_PROCESSING);
if (!string.IsNullOrWhiteSpace(Result))
{
var msg = $"Error during data backup \"{Result}\"";