--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pg-backup Tue Jul 28 16:23:33 2015 +0200
@@ -0,0 +1,172 @@
+#! /usr/bin/perl
+# (c) 2015 Heiko Schlittermann <hs@schlittermann.de>
+use strict;
+use 5.10.0;
+use warnings;
+use Pod::Usage;
+use Getopt::Long;
+use File::Spec::Functions;
+ use Data::Dumper;
+
+my $opt_tasks;
+my $opt_dry;
+my $opt_keep = undef;
+
+my $opt_basedir = '.';
+my $opt_dirname = '$version-$cluster-$date';
+
+delete @ENV{grep /^LC_/ => keys %ENV};
+
+exit main() if not caller;
+
+sub main {
+
+ GetOptions(
+ 'k|keep' => \$opt_keep,
+ 'd|basedir=s' => \$opt_basedir,
+ 'dry' => \$opt_dry,
+ 'h|help' => sub { pod2usage(-verbose => 1, -exit => 0) },
+ 'm|man' => sub { pod2usage(-verbose => 2, -exit => 0,
+ -noperldoc => system('perldoc -V >/dev/null 2>&1')) },
+ );
+
+ my @clusters = ls_clusters();
+
+ my @tasks = sort do {
+ if (@ARGV) {
+ my $v = $ARGV[0];
+ if (@ARGV > 1) {
+ my %h = map { $_->{cluster} => $_ } grep { $_->{version} eq $v } @clusters;
+ @h{@ARGV[1..$#ARGV]};
+ }
+ else {
+ grep { $_->{version} eq $v } @clusters;
+ }
+ }
+ else {
+ grep { $_->{status} eq 'online' } @clusters;
+ }
+ };
+
+ foreach my $task (@tasks) {
+ my $dirname = do {
+ state $date = date();
+ my $version = $task->{version};
+ my $cluster = $task->{cluster};
+ eval "\"$opt_dirname\"";
+ };
+ $task->{dirname} = catfile($opt_basedir, $dirname);
+ }
+
+ # now get the real jobe done
+ my @results;
+ foreach my $task (@tasks) {
+
+ rmdir glob("$opt_basedir/*");
+ mkdir $task->{dirname}
+ or die "$0: Can't mkdir $task->{dirname}: $!\n";
+
+ my @cmd = (pg_basebackup =>
+ '--format' => 't',
+ '--xlog',
+ '--cluster' => $task->{name},
+ '--pgdata' => $task->{dirname},
+ '--gzip',
+ -t 0 ? '--progress' : ());
+
+ if ($opt_dry) {
+ print sprintf "%s %s\n", $task->{name}, "@cmd";
+ next;
+ }
+
+ system @cmd;
+ warn "$0: `@cmd` failed\n" if $?;
+ push @results, { task => $task, exit => $? };
+ }
+
+ # check the results
+
+ foreach (@results) {
+ printf "%-10s %-20s %s\n",
+ $_->{exit} ? "FAIL:$_->{exit}" : "OK",
+ $_->{task}{cluster}{name},
+ $_->{task}{dirname};
+ }
+
+ return 1 if grep { $_->{exit} != 0 } @results;
+ return 0 if not $opt_keep;
+
+ # care about the backups to keep, if everything went fine so far
+
+}
+
+sub date {
+ my @now = localtime;
+ sprintf "%4d-%02d-%02dT%02d-%02d-%02d",
+ $now[5]+1900,
+ $now[4]+1,
+ $now[3],
+ @now[reverse 0..2];
+}
+
+sub ls_clusters {
+ my @clusters;
+
+ foreach ( map { [(split)[0..3]]} `pg_lsclusters -h` ) {
+ push @clusters, {
+ name => "$_->[0]/$_->[1]",
+ version => $_->[0],
+ cluster => $_->[1],
+ port => $_->[2],
+ status => $_->[3],
+ };
+ }
+ return @clusters;
+};
+
+exit main() if not caller;
+
+
+__END__
+
+=head1 NAME
+
+ pg-backup - backup all active PostgreSQL clusters
+
+=head1 SYNOPSIS
+
+ pg-backup [options] [<version> [<cluster>]...]
+
+=head1 DESCRIPTION
+
+for all active PostgreSQL clusters and writes the backups as TAR archives.
+
+If a I<version> and optionally I<cluster>s are specified on the command line,
+all clusters (or the specified clusters) of the I<version> are saved.
+
+Without any I<version>/I<cluster> specification all B<online> clusters are backed up.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-d>|B<--basedir> I<dir>
+
+The directory where the subdirectories for the backups will be placed. (default: F<.>)
+
+=item B<-k>|B<--keep> I<n>
+
+The number of generations to keep. Old backups will be only removed if B<all>
+specified backups succeed. (no default)
+
+=item B<--dry>
+
+Dry run, show what will be done. (default: undef)
+
+=back
+
+=head1 AUTHOR
+
+Heiko Schlittermann L<mailto:hs@schlittermann.de>
+
+=cut