PostgreSQL Backups with Pre-Scripts

I’ve been wanting to play around more with pre-scripting in PowerProtect Data Manager. The proof-of-concept style example I gave last time after all pretty much existed just to give the general concept. So I thought — what about giving an example of using pre-scripts to execute PostgreSQL dumps ahead of a backup? And then I thought … well, why not do it on Windows?

The first step towards doing this was to get PostgreSQL installed on a Windows server and a couple of databases created on it:

Windows desktop showing pgAdmin4 running with 3 databases visible - PMdG, SecondDB and postgres.
pgAdmin 4 on Windows (RDP client: remmina)

On the system, I setup a G:\ drive to hold my PostgreSQL dumps. The goal: configure G:\ as an asset into a policy where the pre-script populates G:\ with the database backups before running the actual backup to Data Domain.

I wanted to test this out manually first, so I started setting up for the testing by creating a pgpass.conf file into %AppData%\postgresql. By having this file present, you don’t have to supply a password to your various pg_ commands, essential for non-interactive backup operations. This uses the format host:port:database:username:password. On my system, that looked like this:

localhost:5432:*:postgres:superSecretPassword

That meant that by running my script and invoking the connection as the postgres user, I’d automatically supply the correct password to any database I connected to. (Obviously, you want to store such a file in a secure location that unauthorised users can’t access.)

Next, the backup batch file — just for a single database for the time being, which looked like the following:

C:\Users\Administrator>type backup.bat
@SET PATH=C:\Program Files\PostgreSQL\16\bin;%PATH%
@SET PGDATA=F:\PostgreSQL\16\data
@SET PGDATABASE=postgres
@SET PGUSER=postgres
@SET PGPORT=5432
@SET PGLOCALEDIR=C:\Program Files\PostgreSQL\16\share\locale
pg_dump -d PMdG -U postgres > G:\PMdG\Backup.sql

That’s my starting point. Now, note I’m not going any compression here of the dump file; since my intention is to send this to Data Domain, I don’t want to interfere with storage efficiency, after all.

That script worked for me, so with the basic concept in place I was ready to set this up as a policy with pre-script in Data Manager.

So in Data Manager, I went to Protection > Scripts and created a new backup script that did pretty much what my script above did:

PowerProtect Data Manager Script Editing Window showing a basic database backup script similar to the script shown earlier in this blog.
Entered script

There are minimum differences between the script entered for a Data Manager policy, and the batch script I created on the system. You’ll note that I’ve thrown in an “exit 0” at the end of the script to make sure I signal completion of the backup. I’ve also added an extra environment variable to explicitly set the PGPASSFILE location, as that needs be explicitly set up when running as a pre- or post-script.

Within the script, the key settings included — asset selection:

Data Manager Asset Selection showing a single selected asset, G:\
Assigning the G:\ asset to the policy

And of course, the actual pre-script configuration itself:

Configuration of the pre-script within Data Manager. The script has been selected, and Windows credentials have been set to allow the script to run. It is configured to time out after 15 minutes and the job will be skipped if the pre-script does not execute successfully.
Pre-script configuration for policy.

The only other modification was to make sure the backed-up data was indexed:

Policy backup options showing indexing enabled for the backup
Indexing Enabled for Policy

When the policy ran, my database dump for the ‘PMdG’ database was successfully generated into G:\PMdG and the file was then backed up by Data Manager. Success!

Now, my batch scripting is a little basic and I wanted to do something a bit more involved — I wanted a backup script that detected all the databases on the system (minus the template databases/postgres database) so that if I added more databases to the system, they’d be automatically backed up. I got cracking and installed Strawberry Perl on my Windows server, and got to work on a more advanced backup script. After a little bit of testing, I ended up with the following script:

#!/usr/bin/perl -w

###########
### Modules
###########
use strict;
use File::Basename;
use Sys::Hostname;


############
### Env Vars
############
$ENV{PATH}="C:\\Program Files\\PostgreSQL\\16\\bin;%PATH%";
$ENV{PGDATA}="F:\\PostgreSQL\\16\\data";
$ENV{PGDATABASE}="postgres";
$ENV{PGUSER}="postgres";
$ENV{PGPORT}=5432;
$ENV{PGLOCALEDIR}="C:\\Program Files\\PostgreSQL\\16\\share\\locale";
$ENV{PGPASSFILE}="C:\\Users\\Administrator\\AppData\\Roaming\\postgresql\\pgpass.conf";

###############
### Global Vars
###############
my @excludeDBs = ("postgres","template1","template0");
my @databasesToProtect = ();
my $dbUser = "postgres";
my $backupBase = "G:\\";
my $keepPrevious = 1;


#############
### Functions
#############

# in_list($expr,@list) returns true iff $expr is in list.
sub in_list {
	return 0 if (@_+0 < 2);

	my $element = $_[0];
	return 0 if (!defined($element));
	shift @_;

	my @list = @_;
	return 0 if (!@_ || @_+0 == 0);

	my $foundCount = 0;
	my $e = quotemeta($element);
	foreach my $item (@list) {
		my $i = quotemeta($item);
		$foundCount++ if ($e eq $i);
	}

	return $foundCount;
}

# excluded($dbName) returns true if the database name is in the excluded database list
sub excluded {
	return 0 if (@_+0 != 1);
	
	# else
	my $database = $_[0];
	if (in_list($database,@excludeDBs)) {
		return 1;
	} else {
		return 0;
	}
}

# keep_previous (database,path) retains the previous copy if we need to.
sub keep_previous {
	return 0 if (@_+0 != 2);
	
	my $dbName = $_[0];
	my $backupPath = $_[1];
	
	return 1 if (!$keepPrevious);	# Nothing special to do here.
	
	my $backupFile = $backupPath . "\\Backup-" . $dbName . ".sql";
	my $prevBackupFile = $backupPath . "\\Backup-" . $dbName . "-prev.sql";
	
	if (-f $prevBackupFile) {
		if (!unlink($prevBackupFile)) {
			print "Could not remove $prevBackupFile, unsafe to continue.\n";
			return 0;
		}
	}
	if (!rename($backupFile,$prevBackupFile)) {
		print "Could not rename $backupFile to $prevBackupFile, unsafe to continue.\n";
		return 0;
	}
	
	# If everything is OK.
	return 1;
}


########
### MAIN
########
if (open(DBS,"echo select datname from pg_database | psql -U $dbUser -A -F, -t 2>&1 |")) {
	while (<DBS>) {
		my $line = $_;
		chomp $line;
		my $dbName = (split(/,/,$line))[0];
		if (!excluded($dbName)) {
			print "Found database: $dbName\n";
			push(@databasesToProtect,$dbName);
		}
	}
	close(DBS);
}

foreach my $db (@databasesToProtect) {
	my $backupPath = $backupBase . "\\" . $db;
	print "Backup $db into $backupPath\n";
	if (! -d $backupPath) {
		print "Backup $db to $backupPath -- create directory\n";
		if (mkdir($backupPath)) {
			print "...successfully created!\n";
		} else {
			print "...whoopsie!\n";
		}
	}
	
	my $safe = keep_previous($db,$backupPath);
	if (!$safe) {
		print "Unsafe to continue for $db/$backupPath, keep_previous was not happy.\n";
	}
	
	# OK, we can back this baby up.
	my $backupFile = $backupPath . "\\" . "Backup-$db.sql";
	print "--> Starting pg_dump as of " . localtime(time) . "\n";
	system("pg_dump -d $db -U $dbUser > $backupFile");
	print "--> pg_dump finished as of " . localtime(time) . "\n\n\n";
}

Now, pulling this script into the policy is more a case of writing a basic one-line script to invoke it. I saved it to the Windows database server in C:\Users\Administrator as pg_backup.pl, and configured the Data Manager script as follows:

A Data Manager script that invokes a local backup command on the Windows system -- the Perl script previously shown
Invoking the Perl script for the backup pre-script

I then just needed to change the pre-script in the policy to this new one:

Amended Data Manager policy configuration that invokes the Perl script instead.
Amended Data Manager Pre-Script settings in Policy

That was it! And the proof is in the file-search pudding:

Data Manager File Search showing a number of database dumps ready for restore
File Restore Search Results

And there you have it! The beauty for me in pre-scripts is that they truly do massively increase your horizon for options within your protection product.


Hey, I’m still running a book giveaway competition! I’d love it if you won.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.