-#!/usr/bin/perl -w
+#!/usr/bin/perl -ws
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to:
+#
+# Free Software Foundation, Inc.
+# 59 Temple Place - Suite 330
+# Boston, MA 02111-1307, USA.
+
+# Rudimentary switch parsing. Must be in main package.
+our $cleanup;
+
+package BBM;
use strict;
use POSIX qw(setuid setgid);
-
use DBI;
use File::Temp qw(tempdir);
use File::Path qw(rmtree);
-use delfi::mycnf;
-# path to ini-style config
+
+# path to Apache HTTPd-style config
my $config = '/etc/bacula/backup-mysql.conf';
-my $c = new delfi::mycnf($config);
-# force reading config file before we switch user
-$c->get("");
+my $c = new BBM::Config($config);
-# how change to user mysql after we've read config
+# now change to user mysql after we've read config
unless ($<) {
my $uid = getpwnam('mysql');
my $gid = getgrnam('mysql');
die "Can't find user/group mysql\n" unless $uid or $gid;
+ # CWD could not be accessible for mysql user
+ chdir("/");
+
$) = "$gid $gid";
$( = $gid;
$> = $< = $uid;
# process each cluster
for my $cluster ($c->get('clusters', 'cluster')) {
print ">>> $cluster\n";
- backup_cluster($cluster);
+ if ($cleanup) {
+ cleanup_cluster($cluster);
+ } else {
+ backup_cluster($cluster);
+ }
print "<<< $cluster\n";
}
-#
+#
# Usage: mysqlhotcopy $CLUSTER $DATABASE $USERNAME $PASSWORD $SOCKET
#
sub mysqlhotcopy {
my ($cluster, $database, $user, $password, $socket) = @_;
+ print ">>>> mysqlhotcopy $database\n";
+
my $dstdir = tempdir("bbm.XXXXXX", DIR => $tmpdir);
# make backup with mysqlhotcopy
# put it to "production dir"
my $cluster_dir = "$backup_dir/$cluster";
if (!-d $cluster_dir && !mkdir($cluster_dir) && !-d $cluster_dir) {
+ rmtree($dstdir);
die "cluster dir '$cluster_dir' not present and can't be created\n";
}
rmtree($dirname);
}
- my $srcdir= "$dstdir/$database";
- rename($srcdir, $dirname) or die "Rename '$srcdir'->'$dirname' failed: $!\n";
+ my $srcdir = "$dstdir/$database";
+ unless (rename($srcdir, $dirname)) {
+ rmtree($dstdir);
+ die "Rename '$srcdir'->'$dirname' failed: $!\n";
+ }
rmdir($dstdir) or warn $!;
+
+ print "<<<< mysqlhotcopy $database\n";
+}
+
+sub cleanup_cluster {
+ my ($cluster) = @_;
+ my $cluster_dir = "$backup_dir/$cluster";
+ print ">>>> cleanup $cluster_dir\n";
+ rmtree($cluster_dir);
+ print "<<<< cleanup $cluster_dir\n";
}
sub backup_cluster {
my $socket = $c->get($cluster, 'socket') || $c->get('client', 'socket');
# get databases to backup
- my @dbs;
+ my @include = $c->get($cluster, 'include_database');
+ my @exclude = $c->get($cluster, 'exclude_database');
+
+ # start with include list
+ my %dbs = map { $_ => 1 } @include;
- my @include = $c->get($cluster, 'include_databases');
- my @exclude = $c->get($cluster, 'exclude_databases');
if (@exclude or !@include) {
- my $dbh = new DB($user, $password, $socket);
+ my $dbh = new BBM::DB($user, $password, $socket);
my $sth = $dbh->prepare("show databases");
$sth->execute();
while (my($dbname) = $sth->fetchrow_array) {
next if lc($dbname) eq 'information_schema';
- push @dbs, $dbname unless grep { m{^\Q$dbname\E$} } @exclude;
+ $dbs{$dbname} = 1;
}
undef $dbh;
}
+ # remove excluded databases
+ delete @dbs{@exclude};
+
# now do the backup
- foreach my $db (@dbs) {
+ foreach my $db (keys %dbs) {
mysqlhotcopy($cluster, $db, $user, $password, $socket);
}
}
-package DB;
+package BBM::DB;
+use strict;
# DB class for simple Database connection
sub new {
my $dbh = DBI->connect("DBI:mysql:$dsn", $user, $password, { PrintError => 0, RaiseError => 1 });
return $dbh;
}
+
+package BBM::Config;
+use strict;
+use Config::General;
+
+sub new {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ my $file = shift;
+
+ my $config = new Config::General(-ConfigFile => $file, -LowerCaseNames => 1);
+ my $this = { $config->getall() };
+ bless($this, $class);
+}
+
+sub get {
+ my ($self, $section, $key) = @_;
+ my $h = $self;
+
+ # descend to [cluster] if $section not present in root tree
+ unless (exists $h->{$section}) {
+ $h = $h->{cluster};
+ }
+
+ # pay attention if callee wanted arrays
+ return wantarray ? () : undef unless exists $h->{$section};
+ return wantarray ? () : undef unless exists $h->{$section}->{$key};
+
+ # deref if wanted array and is arrayref
+ return @{$h->{$section}->{$key}} if wantarray && ref $h->{$section}->{$key} eq 'ARRAY';
+
+ return $h->{$section}->{$key};
+}
+
+
+__END__
+
+=head1 NAME
+
+bacula-backup-mysql - A hook for Bacula to backup mysql databases using mysqlhotcopy.
+
+=head1 SYNOPSIS
+
+ Job {
+ Name = "example.org-mysql"
+ ...
+ # This prepares the backup
+ Client Run Before Job = "/usr/sbin/bacula-backup-mysql"
+ # This deletes the copy of the catalog
+ Client Run After Job = "/usr/sbin/bacula-backup-mysql -cleanup"
+ }
+
+=head1 DESCRIPTION
+
+This is a script to be setup as C<Client Run Before Job> in Bacula.
+
+=head1 AUTHOR
+
+Copyright (C) 2009-2010, Elan RuusamE<auml>e <glen@delfi.ee>
+
+=head1 SEE ALSO
+
+http://www.bacula.org/
+
+=cut