]> git.proxmox.com Git - pmg-api.git/blame - src/PMG/Backup.pm
backup: fix die invocation
[pmg-api.git] / src / PMG / Backup.pm
CommitLineData
9a8d51a4
DM
1package PMG::Backup;
2
3use strict;
4use warnings;
5use Data::Dumper;
6use File::Basename;
7use File::Path;
8
cc9a5f58 9use PVE::JSONSchema qw(get_standard_option);
9a8d51a4
DM
10use PVE::Tools;
11
12use PMG::pmgcfg;
13use PMG::AtomicFile;
6529020a 14use PMG::Utils qw(postgres_admin_cmd);
9a8d51a4 15
dd290005
SI
16my $sa_configs = [
17 "/etc/mail/spamassassin/custom.cf",
18 "/etc/mail/spamassassin/pmg-scores.cf",
19];
520f7717 20
cc9a5f58
SI
21sub get_restore_options {
22 return (
23 node => get_standard_option('pve-node'),
24 config => {
25 description => "Restore system configuration.",
26 type => 'boolean',
27 optional => 1,
28 default => 0,
29 },
30 database => {
31 description => "Restore the rule database. This is the default.",
32 type => 'boolean',
33 optional => 1,
34 default => 1,
35 },
36 statistic => {
37 description => "Restore statistic databases. Only considered when you restore the 'database'.",
38 type => 'boolean',
39 optional => 1,
40 default => 0,
41 });
42}
43
9a8d51a4
DM
44sub dump_table {
45 my ($dbh, $table, $ofh, $seq, $seqcol) = @_;
46
47 my $sth = $dbh->column_info(undef, undef, $table, undef);
48
49 my $attrs = $sth->fetchall_arrayref({});
50
51 my @col_arr;
52 foreach my $ref (@$attrs) {
53 push @col_arr, $ref->{COLUMN_NAME};
54 }
55
56 $sth->finish();
57
58 my $cols = join (', ', @col_arr);
59 $cols || die "unable to fetch column definitions: ERROR";
60
61 print $ofh "COPY $table ($cols) FROM stdin;\n";
62
63 my $cmd = "COPY $table ($cols) TO STDOUT";
64 $dbh->do($cmd);
65
66 my $data = '';
67 while ($dbh->pg_getcopydata($data) >= 0) {
68 print $ofh $data;
69 }
70
71 print $ofh "\\.\n\n";
72
73 if ($seq && $seqcol) {
74 print $ofh "SELECT setval('$seq', max($seqcol)) FROM $table;\n\n";
75 }
76}
77
78sub dumpdb {
79 my ($ofh) = @_;
80
81 print $ofh "SET client_encoding = 'SQL_ASCII';\n";
82 print $ofh "SET check_function_bodies = false;\n\n";
83
84 my $dbh = PMG::DBTools::open_ruledb();
85
86 print $ofh "BEGIN TRANSACTION;\n\n";
87
88 eval {
89 $dbh->begin_work;
90
91 # read a consistent snapshot
92 $dbh->do("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
93
94 dump_table($dbh, 'attribut', $ofh);
95 dump_table($dbh, 'object', $ofh, 'object_id_seq', 'id');
96 dump_table($dbh, 'objectgroup', $ofh, 'objectgroup_id_seq', 'id');
97 dump_table($dbh, 'rule', $ofh, 'rule_id_seq', 'id');
98 dump_table($dbh, 'rulegroup', $ofh);
99 dump_table($dbh, 'userprefs', $ofh);
100
101 # we do not save the following tables: cgreylist, cmailstore, cmsreceivers, clusterinfo
102 };
103 my $err = $@;
104
105 $dbh->rollback(); # end read-only transaction
106
107 $dbh->disconnect();
108
109 die $err if $err;
110
111 print $ofh "COMMIT TRANSACTION;\n\n";
112}
113
114sub dumpstatdb {
115 my ($ofh) = @_;
116
117 print $ofh "SET client_encoding = 'SQL_ASCII';\n";
118 print $ofh "SET check_function_bodies = false;\n\n";
119
120 my $dbh = PMG::DBTools::open_ruledb();
121
122 eval {
123 $dbh->begin_work;
124
125 # read a consistent snapshot
126 $dbh->do("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
127
128 print $ofh "BEGIN TRANSACTION;\n\n";
129
130 dump_table($dbh, 'dailystat', $ofh);
131 dump_table($dbh, 'domainstat', $ofh);
132 dump_table($dbh, 'virusinfo', $ofh);
133 dump_table($dbh, 'localstat', $ofh);
134
135 # drop/create the index is a little bit faster (20%)
136
137 print $ofh "DROP INDEX cstatistic_time_index;\n\n";
138 print $ofh "ALTER TABLE cstatistic DROP CONSTRAINT cstatistic_id_key;\n\n";
139 print $ofh "ALTER TABLE cstatistic DROP CONSTRAINT cstatistic_pkey;\n\n";
140 dump_table($dbh, 'cstatistic', $ofh, 'cstatistic_id_seq', 'id');
141 print $ofh "ALTER TABLE ONLY cstatistic ADD CONSTRAINT cstatistic_pkey PRIMARY KEY (cid, rid);\n\n";
142 print $ofh "ALTER TABLE ONLY cstatistic ADD CONSTRAINT cstatistic_id_key UNIQUE (id);\n\n";
143 print $ofh "CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);\n\n";
144
145 print $ofh "DROP INDEX CStatistic_ID_Index;\n\n";
146 dump_table($dbh, 'creceivers', $ofh);
147 print $ofh "CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);\n\n";
148
149 dump_table($dbh, 'statinfo', $ofh);
150
151 print $ofh "COMMIT TRANSACTION;\n\n";
152 };
153 my $err = $@;
154
155 $dbh->rollback(); # end read-only transaction
156
157 $dbh->disconnect();
158
159 die $err if $err;
160}
161
708cc7a9 162# this function assumes that directory $dirname exists and is empty
9a8d51a4 163sub pmg_backup {
29e2ff48
SI
164 my ($dirname, $include_statistics) = @_;
165
166 die "No backupdir provided!\n" if !defined($dirname);
9a8d51a4
DM
167
168 my $time = time;
9a8d51a4
DM
169 my $dbfn = "Proxmox_ruledb.sql";
170 my $statfn = "Proxmox_statdb.sql";
171 my $tarfn = "config_backup.tar";
172 my $sigfn = "proxmox_backup_v1.md5";
173 my $verfn = "version.txt";
174
175 eval {
176
9a8d51a4
DM
177 # dump the database first
178 my $fh = PMG::AtomicFile->open("$dirname/$dbfn", "w") ||
179 die "cant open '$dirname/$dbfn' - $! :ERROR";
180
181 dumpdb($fh);
182
183 $fh->close(1);
184
185 if ($include_statistics) {
186 # dump the statistic db
187 my $sfh = PMG::AtomicFile->open("$dirname/$statfn", "w") ||
188 die "cant open '$dirname/$statfn' - $! :ERROR";
189
190 dumpstatdb($sfh);
191
192 $sfh->close(1);
193 }
194
195 my $pkg = PMG::pmgcfg::package();
1c15ce0f 196 my $release = PMG::pmgcfg::release();
9a8d51a4
DM
197
198 my $vfh = PMG::AtomicFile->open ("$dirname/$verfn", "w") ||
199 die "cant open '$dirname/$verfn' - $! :ERROR";
200
201 $time = time;
202 my $now = localtime;
1c15ce0f 203 print $vfh "product: $pkg\nversion: $release\nbackuptime:$time:$now\n";
9a8d51a4
DM
204 $vfh->close(1);
205
bd1325ed 206 my $extra_cfgs = [];
9a8d51a4 207
dd290005 208 push @$extra_cfgs, @{$sa_configs};
9a8d51a4 209
9a8d51a4
DM
210 my $extradb = $include_statistics ? $statfn : '';
211
bd1325ed 212 my $extra = join(' ', @$extra_cfgs);
9a8d51a4
DM
213
214 system("/bin/tar cf $dirname/$tarfn -C / " .
bd1325ed 215 "/etc/pmg $extra>/dev/null 2>&1") == 0 ||
9a8d51a4
DM
216 die "unable to create system configuration backup: ERROR";
217
218 system("cd $dirname; md5sum $tarfn $dbfn $extradb $verfn> $sigfn") == 0 ||
219 die "unable to create backup signature: ERROR";
220
29e2ff48
SI
221 };
222 my $err = $@;
223
224 if ($err) {
225 die $err;
226 }
227}
228
229sub pmg_backup_pack {
230 my ($filename, $include_statistics) = @_;
231
232 my $time = time;
233 my $dirname = "/tmp/proxbackup_$$.$time";
234
235 eval {
236
237 my $targetdir = dirname($filename);
238 mkdir $targetdir; # try to create target dir
239 -d $targetdir ||
240 die "unable to access target directory '$targetdir'\n";
241
242 rmtree $dirname;
243 # create backup directory
244 mkdir $dirname;
245
246 pmg_backup($dirname, $include_statistics);
247
248 system("rm -f $filename; tar czf $filename --strip-components=1 -C $dirname .") == 0 ||
f44d0cae 249 die "unable to create backup archive: ERROR\n";
9a8d51a4
DM
250 };
251 my $err = $@;
252
253 rmtree $dirname;
254
255 if ($err) {
256 unlink $filename;
257 die $err;
258 }
259}
260
a6d276e9
DM
261sub pmg_restore {
262 my ($filename, $restore_database, $restore_config, $restore_statistics) = @_;
263
264 my $dbname = 'Proxmox_ruledb';
265
266 my $time = time;
267 my $dirname = "/tmp/proxrestore_$$.$time";
268 my $dbfn = "Proxmox_ruledb.sql";
269 my $statfn = "Proxmox_statdb.sql";
270 my $tarfn = "config_backup.tar";
271 my $sigfn = "proxmox_backup_v1.md5";
272
2cf02541
SI
273 my $untar = 1;
274
275 # directory indicates that the files were restored from a PBS remote
276 if ( -d $filename ) {
277 $dirname = $filename;
278 $untar = 0;
279 }
280
a6d276e9 281 eval {
a6d276e9 282
2cf02541 283 if ($untar) {
e9c4929f
DM
284 # remove any leftovers
285 rmtree $dirname;
2cf02541
SI
286 # create a temporary directory
287 mkdir $dirname;
288
289 system("cd $dirname; tar xzf $filename >/dev/null 2>&1") == 0 ||
290 die "unable to extract backup archive: ERROR";
291 }
a6d276e9
DM
292
293 system("cd $dirname; md5sum -c $sigfn") == 0 ||
294 die "proxmox backup signature check failed: ERROR";
295
296 if ($restore_config) {
297 # restore the tar file
298 mkdir "$dirname/config/";
299 system("tar xpf $dirname/$tarfn -C $dirname/config/") == 0 ||
300 die "unable to restore configuration tar archive: ERROR";
301
302 -d "$dirname/config/etc/pmg" ||
303 die "backup does not contain a valid system configuration directory (/etc/pmg)\n";
304 # unlink unneeded files
305 unlink "$dirname/config/etc/pmg/cluster.conf"; # never restore cluster config
306 rmtree "$dirname/config/etc/pmg/master";
307
8624613e
DM
308 # remove current config, but keep directory for INotify
309 rmtree("/etc/pmg", { keep_root => 1 });
a6d276e9
DM
310 # copy files
311 system("cp -a $dirname/config/etc/pmg/* /etc/pmg/") == 0 ||
312 die "unable to restore system configuration: ERROR";
313
dd290005
SI
314 for my $sa_cfg (@{$sa_configs}) {
315 if (-f "$dirname/config/${sa_cfg}") {
316 my $data = PVE::Tools::file_get_contents(
317 "$dirname/config/${sa_cfg}", 1024*1024);
318 PVE::Tools::file_set_contents($sa_cfg, $data);
319 }
520f7717
DM
320 }
321
a6d276e9
DM
322 my $cfg = PMG::Config->new();
323 my $ruledb = PMG::RuleDB->new();
324 my $rulecache = PMG::RuleCache->new($ruledb);
325 $cfg->rewrite_config($rulecache, 1);
326 }
327
328 if ($restore_database) {
329 # recreate the database
330
331 # stop all services accessing the database
332 PMG::Utils::service_wait_stopped(40, $PMG::Utils::db_service_list);
333
334 print "Destroy existing rule database\n";
335 PMG::DBTools::delete_ruledb($dbname);
336
337 print "Create new database\n";
338 my $dbh = PMG::DBTools::create_ruledb($dbname);
a6d276e9
DM
339
340 system("cat $dirname/$dbfn|psql $dbname >/dev/null 2>&1") == 0 ||
341 die "unable to restore rule database: ERROR";
342
343 if ($restore_statistics) {
344 if (-f "$dirname/$statfn") {
345 system("cat $dirname/$statfn|psql $dbname >/dev/null 2>&1") == 0 ||
346 die "unable to restore statistic database: ERROR";
347 }
348 }
349
350 print STDERR "run analyze to speed up database queries\n";
6529020a 351 postgres_admin_cmd('psql', { input => 'analyze;' }, $dbname);
a6d276e9
DM
352
353 print "Analyzing/Upgrading existing Databases...";
9882b397 354 my $ruledb = PMG::RuleDB->new($dbh);
a6d276e9
DM
355 PMG::DBTools::upgradedb($ruledb);
356 print "done\n";
357
358 # cleanup old spam/virus storage
359 PMG::MailQueue::create_spooldirs(0, 1);
360
361 my $cfg = PMG::Config->new();
362 my $rulecache = PMG::RuleCache->new($ruledb);
363 $cfg->rewrite_config($rulecache, 1);
364
365 # and restart services as soon as possible
366 foreach my $service (reverse @$PMG::Utils::db_service_list) {
367 eval { PVE::Tools::run_command(['systemctl', 'start', $service]); };
368 warn $@ if $@;
369 }
370 }
371 };
372 my $err = $@;
373
374 rmtree $dirname;
375
376 die $err if $err;
377}
378
9a8d51a4 3791;