]>
Commit | Line | Data |
---|---|---|
1d217da6 | 1 | #!/usr/bin/perl -ws |
46478424 ER |
2 | # This program is free software; you can redistribute it and/or modify |
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation; either version 2 of the License, or | |
5 | # (at your option) any later version. | |
6 | # | |
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
36d65868 | 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
46478424 ER |
10 | # GNU General Public License for more details. |
11 | # | |
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program; if not, write to: | |
14 | # | |
15 | # Free Software Foundation, Inc. | |
16 | # 59 Temple Place - Suite 330 | |
17 | # Boston, MA 02111-1307, USA. | |
18 | ||
1d217da6 ER |
19 | # Rudimentary switch parsing. Must be in main package. |
20 | our $cleanup; | |
21 | ||
38d6af1e | 22 | package BBM; |
44be95f6 ER |
23 | use strict; |
24 | use POSIX qw(setuid setgid); | |
44be95f6 ER |
25 | use DBI; |
26 | use File::Temp qw(tempdir); | |
27 | use File::Path qw(rmtree); | |
44be95f6 | 28 | |
947fde71 ER |
29 | # path to Apache HTTPd-style config |
30 | my $config = '/etc/bacula/backup-mysql.conf'; | |
38d6af1e | 31 | my $c = new BBM::Config($config); |
44be95f6 | 32 | |
7b895eeb | 33 | # now change to user mysql after we've read config |
44be95f6 ER |
34 | unless ($<) { |
35 | my $uid = getpwnam('mysql'); | |
36 | my $gid = getgrnam('mysql'); | |
37 | die "Can't find user/group mysql\n" unless $uid or $gid; | |
38 | ||
1d217da6 ER |
39 | # CWD could not be accessible for mysql user |
40 | chdir("/"); | |
41 | ||
44be95f6 | 42 | $) = "$gid $gid"; |
36d65868 ER |
43 | $( = $gid; |
44 | $> = $< = $uid; | |
44be95f6 ER |
45 | } |
46 | ||
47 | # setup tmpdir | |
48 | my $backup_dir = $c->get('options', 'outdir') or die "'outdir' not defined in config\n"; | |
36d65868 | 49 | my $tmpdir = $c->get('options', 'tmpdir') or die "'tmpdir' not defined in config\n"; |
44be95f6 ER |
50 | |
51 | if (!-d $backup_dir && !mkdir($backup_dir) && !-d $backup_dir) { | |
52 | die "backup dir '$backup_dir' not present and can't be created\n"; | |
53 | } | |
54 | if (!-d $tmpdir && !mkdir($tmpdir) && !-d $tmpdir) { | |
55 | die "tmpdir '$tmpdir' not present and can't be created\n"; | |
56 | } | |
57 | ||
58 | # process each cluster | |
59 | for my $cluster ($c->get('clusters', 'cluster')) { | |
21563d32 | 60 | print ">>> cluster: $cluster\n"; |
1d217da6 ER |
61 | if ($cleanup) { |
62 | cleanup_cluster($cluster); | |
63 | } else { | |
64 | backup_cluster($cluster); | |
65 | } | |
21563d32 | 66 | print "<<< end cluster: $cluster\n"; |
44be95f6 ER |
67 | } |
68 | ||
46478424 | 69 | # |
44be95f6 ER |
70 | # Usage: mysqlhotcopy $CLUSTER $DATABASE $USERNAME $PASSWORD $SOCKET |
71 | # | |
72 | sub mysqlhotcopy { | |
36d65868 ER |
73 | my ($cluster, $db, $user, $password, $socket) = @_; |
74 | ||
75 | # strip $database to contain only db name, as the rest of the code assumes $database is just database name | |
76 | # i.e: include_database teensForum5./~(phorum_forums|phorum_users)/ | |
77 | my ($database) = $db =~ /^([^\.]+)/; | |
44be95f6 ER |
78 | |
79 | my $dstdir = tempdir("bbm.XXXXXX", DIR => $tmpdir); | |
80 | ||
21563d32 ER |
81 | # remove output dir before backup, |
82 | # otherwise the disk space requirement would double | |
83 | my $dirname = "$backup_dir/$cluster/$database"; | |
84 | if (-d $dirname) { | |
85 | print ">>>> rmtree $dirname\n"; | |
86 | rmtree($dirname); | |
87 | } | |
88 | ||
44be95f6 ER |
89 | # make backup with mysqlhotcopy |
90 | my @shell = ('mysqlhotcopy'); | |
91 | push(@shell, '-u', $user) if $user; | |
92 | push(@shell, '-p', $password) if $password; | |
93 | push(@shell, '-S', $socket) if $socket; | |
36d65868 | 94 | push(@shell, $db, $dstdir); |
21563d32 | 95 | print ">>>> mysqlhotcopy $database\n"; |
44be95f6 ER |
96 | system(@shell) == 0 or die "mysqlhotcopy failed: $?\n"; |
97 | ||
98 | # put it to "production dir" | |
99 | my $cluster_dir = "$backup_dir/$cluster"; | |
100 | if (!-d $cluster_dir && !mkdir($cluster_dir) && !-d $cluster_dir) { | |
49da4557 | 101 | rmtree($dstdir); |
44be95f6 ER |
102 | die "cluster dir '$cluster_dir' not present and can't be created\n"; |
103 | } | |
104 | ||
49da4557 ER |
105 | my $srcdir = "$dstdir/$database"; |
106 | unless (rename($srcdir, $dirname)) { | |
9a263aad | 107 | my $err = $!; |
49da4557 | 108 | rmtree($dstdir); |
9a263aad | 109 | die "Rename '$srcdir'->'$dirname' failed: $err\n"; |
49da4557 | 110 | } |
44be95f6 ER |
111 | |
112 | rmdir($dstdir) or warn $!; | |
62f7c875 ER |
113 | |
114 | print "<<<< mysqlhotcopy $database\n"; | |
44be95f6 ER |
115 | } |
116 | ||
1d217da6 ER |
117 | sub cleanup_cluster { |
118 | my ($cluster) = @_; | |
119 | my $cluster_dir = "$backup_dir/$cluster"; | |
120 | print ">>>> cleanup $cluster_dir\n"; | |
121 | rmtree($cluster_dir); | |
122 | print "<<<< cleanup $cluster_dir\n"; | |
123 | } | |
124 | ||
44be95f6 ER |
125 | sub backup_cluster { |
126 | my ($cluster) = @_; | |
127 | ||
128 | # get db connection info | |
129 | my $user = $c->get($cluster, 'user') || $c->get('client', 'user'); | |
130 | my $password = $c->get($cluster, 'password') || $c->get('client', 'password'); | |
131 | my $socket = $c->get($cluster, 'socket') || $c->get('client', 'socket'); | |
132 | ||
133 | # get databases to backup | |
38d6af1e ER |
134 | my @include = $c->get($cluster, 'include_database'); |
135 | my @exclude = $c->get($cluster, 'exclude_database'); | |
44be95f6 | 136 | |
38d6af1e ER |
137 | # start with include list |
138 | my %dbs = map { $_ => 1 } @include; | |
62f7c875 | 139 | |
44be95f6 | 140 | if (@exclude or !@include) { |
38d6af1e | 141 | my $dbh = new BBM::DB($user, $password, $socket); |
44be95f6 ER |
142 | my $sth = $dbh->prepare("show databases"); |
143 | $sth->execute(); | |
144 | while (my($dbname) = $sth->fetchrow_array) { | |
145 | next if lc($dbname) eq 'information_schema'; | |
949a3e79 | 146 | next if lc($dbname) eq 'performance_schema'; |
38d6af1e | 147 | $dbs{$dbname} = 1; |
44be95f6 ER |
148 | } |
149 | undef $dbh; | |
150 | } | |
151 | ||
38d6af1e ER |
152 | # remove excluded databases |
153 | delete @dbs{@exclude}; | |
154 | ||
44be95f6 | 155 | # now do the backup |
38d6af1e | 156 | foreach my $db (keys %dbs) { |
44be95f6 ER |
157 | mysqlhotcopy($cluster, $db, $user, $password, $socket); |
158 | } | |
159 | } | |
160 | ||
38d6af1e ER |
161 | package BBM::DB; |
162 | use strict; | |
44be95f6 ER |
163 | |
164 | # DB class for simple Database connection | |
165 | sub new { | |
72cc922f | 166 | my $self = shift; |
44be95f6 ER |
167 | my ($user, $password, $socket) = @_; |
168 | my $dsn = ''; | |
169 | $dsn .= "mysql_socket=$socket" if $socket; | |
170 | my $dbh = DBI->connect("DBI:mysql:$dsn", $user, $password, { PrintError => 0, RaiseError => 1 }); | |
171 | return $dbh; | |
172 | } | |
38d6af1e ER |
173 | |
174 | package BBM::Config; | |
175 | use strict; | |
176 | use Config::General; | |
177 | ||
178 | sub new { | |
179 | my $self = shift; | |
180 | my $class = ref($self) || $self; | |
181 | my $file = shift; | |
182 | ||
183 | my $config = new Config::General(-ConfigFile => $file, -LowerCaseNames => 1); | |
184 | my $this = { $config->getall() }; | |
185 | bless($this, $class); | |
186 | } | |
187 | ||
188 | sub get { | |
189 | my ($self, $section, $key) = @_; | |
190 | my $h = $self; | |
191 | ||
192 | # descend to [cluster] if $section not present in root tree | |
193 | unless (exists $h->{$section}) { | |
194 | $h = $h->{cluster}; | |
195 | } | |
196 | ||
197 | # pay attention if callee wanted arrays | |
198 | return wantarray ? () : undef unless exists $h->{$section}; | |
199 | return wantarray ? () : undef unless exists $h->{$section}->{$key}; | |
62f7c875 ER |
200 | |
201 | # deref if wanted array and is arrayref | |
202 | return @{$h->{$section}->{$key}} if wantarray && ref $h->{$section}->{$key} eq 'ARRAY'; | |
203 | ||
38d6af1e ER |
204 | return $h->{$section}->{$key}; |
205 | } | |
46478424 ER |
206 | |
207 | ||
208 | __END__ | |
209 | ||
210 | =head1 NAME | |
211 | ||
212 | bacula-backup-mysql - A hook for Bacula to backup mysql databases using mysqlhotcopy. | |
213 | ||
214 | =head1 SYNOPSIS | |
215 | ||
216 | Job { | |
217 | Name = "example.org-mysql" | |
218 | ... | |
72b32af8 | 219 | # This prepares the backup |
46478424 ER |
220 | Client Run Before Job = "/usr/sbin/bacula-backup-mysql" |
221 | # This deletes the copy of the catalog | |
222 | Client Run After Job = "/usr/sbin/bacula-backup-mysql -cleanup" | |
223 | } | |
224 | ||
225 | =head1 DESCRIPTION | |
226 | ||
227 | This is a script to be setup as C<Client Run Before Job> in Bacula. | |
228 | ||
0183092d ER |
229 | =head1 CONFIGURATION |
230 | ||
231 | Config starts with C<E<lt>clustersE<gt>> block, which you can define several | |
232 | database instances to be backed up. | |
233 | ||
234 | Each C<cluster> should define connection dsn and tables to be backed up. | |
235 | ||
236 | To define connection dsn, you can also define common options in | |
237 | C<E<lt>clientE<gt>> section, those options would be shared for each cluster | |
238 | which has not overriden the specific option. | |
239 | ||
240 | In C<E<lt>clusterE<gt>> sections, you should list databases to be backed up. | |
241 | You can use C<include_database> option to implicitly list which databases to be | |
242 | backed up. If you omit C<include_database> option, all databases are backed up | |
36d65868 | 243 | which can be seen with C<show tables> query. To exclude databases from that |
0183092d ER |
244 | list you can use C<exclude_database> options. |
245 | ||
36d65868 ER |
246 | Additionally C<include_database> supports table regexp, which is passed |
247 | directly to mysqlhotcopy(1). This allows you to include or exclude tables from | |
248 | each database backup. | |
249 | ||
0183092d ER |
250 | =head1 EXAMPLE CONFIGURATION |
251 | ||
252 | # clusters to backup | |
253 | # there is nothing special about name "mysql", name just picked as convience | |
254 | <clusters> | |
255 | cluster mysql | |
36d65868 ER |
256 | cluster eventum |
257 | cluster forums | |
0183092d ER |
258 | </clusters> |
259 | ||
260 | # client connection parameters, can be overriden per cluster | |
261 | <client> | |
262 | user mysql | |
263 | password secret | |
264 | </client> | |
265 | ||
266 | # global options, can be overriden per cluster | |
267 | <options> | |
268 | tmpdir /srv/bacula/tmp | |
269 | outdir /srv/bacula/mysql | |
270 | </options> | |
271 | ||
272 | # mysql cluster: backup all but mysql database | |
273 | <cluster mysql> | |
274 | socket /var/lib/mysql/mysql.sock | |
275 | exclude_database mysql | |
276 | </cluster> | |
277 | ||
36d65868 ER |
278 | # forums cluster: exclude phorum_forums and phorum_users tables |
279 | <cluster forums> | |
280 | user mysql | |
281 | socket /var/lib/mysql/mysql.sock | |
282 | ||
283 | # include_database can also support table regexp for mysqlhotcopy: | |
284 | include_database teensForum5./~(phorum_forums|phorum_users)/ | |
285 | </cluster> | |
286 | ||
46478424 ER |
287 | =head1 AUTHOR |
288 | ||
0183092d | 289 | Copyright (C) 2009-2012, Elan RuusamE<auml>e <glen@delfi.ee> |
46478424 ER |
290 | |
291 | =head1 SEE ALSO | |
292 | ||
293 | http://www.bacula.org/ | |
294 | ||
295 | =cut |