]> git.proxmox.com Git - pmg-api.git/commitdiff
PMG/Backup.pm - implement backup
authorDietmar Maurer <dietmar@proxmox.com>
Mon, 6 Nov 2017 07:58:13 +0000 (08:58 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 6 Nov 2017 07:59:07 +0000 (08:59 +0100)
Makefile
PMG/API2/Backup.pm
PMG/Backup.pm [new file with mode: 0644]

index 72b7bfa13b3359d0c6d054a8399617f553f696f3..defcd5582398e1ae7b767b77928448f085c8fe62 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -73,6 +73,7 @@ LIBSOURCES =                          \
        PMG/Postfix.pm                  \
        PMG/SMTP.pm                     \
        PMG/Unpack.pm                   \
+       PMG/Backup.pm                   \
        PMG/RuleCache.pm                \
        PMG/Statistic.pm                \
        PMG/UserConfig.pm               \
index cb132e00a1287efadc88d0a9b67d5518c48d7ab6..e51cdca02aa8c103af19b0be3aa7ee6c8c8f18eb 100644 (file)
@@ -10,7 +10,9 @@ use PVE::JSONSchema qw(get_standard_option);
 use PVE::RESTHandler;
 use PVE::INotify;
 
+use PMG::RESTEnvironment;
 use PMG::Config;
+use PMG::Backup;
 
 use base qw(PVE::RESTHandler);
 
@@ -63,6 +65,7 @@ __PACKAGE__->register_method ({
     parameters => {
        additionalProperties => 0,
        properties => {
+           node => get_standard_option('pve-node'),
            statistic => $include_statistic_property,
        },
     },
@@ -70,11 +73,28 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
-       die "implement me";
+       my $rpcenv = PMG::RESTEnvironment->get();
+       my $authuser = $rpcenv->get_user();
 
-       my $res = "test";
+       my $bkdir = "/var/lib/pmg/tmp";
 
-       return $res;
+       my (undef, undef, undef, $mday, $mon, $year) = localtime(time);
+       my $bkfile = sprintf("pmg-backup_%04d_%02d_%02d.tgz", $year + 1900, $mon + 1, $mday);
+       my $filename = "$bkdir/$bkfile";
+
+       my $worker = sub {
+           my $upid = shift;
+
+           print "starting backup\n";
+           print "target file: $filename\n";
+
+           PMG::Backup::pmg_backup($filename, $param->{statistic});
+           print "backup finished\n";
+
+           return;
+       };
+
+       return $rpcenv->fork_worker('backup', undef, $authuser, $worker);
     }});
 
 
@@ -89,6 +109,7 @@ __PACKAGE__->register_method ({
     parameters => {
        additionalProperties => 0,
        properties => {
+           node => get_standard_option('pve-node'),
            filename => {
                description => "The backup file you want to retore.",
                type => "string",
@@ -120,3 +141,5 @@ __PACKAGE__->register_method ({
 
        return $res;
     }});
+
+1;
diff --git a/PMG/Backup.pm b/PMG/Backup.pm
new file mode 100644 (file)
index 0000000..db6d159
--- /dev/null
@@ -0,0 +1,222 @@
+package PMG::Backup;
+
+use strict;
+use warnings;
+use Data::Dumper;
+use File::Basename;
+use File::Path;
+
+use PVE::Tools;
+
+use PMG::pmgcfg;
+use PMG::AtomicFile;
+
+sub dump_table {
+    my ($dbh, $table, $ofh, $seq, $seqcol) = @_;
+
+    my $sth = $dbh->column_info(undef, undef, $table, undef);
+
+    my $attrs = $sth->fetchall_arrayref({});
+
+    my @col_arr;
+    foreach my $ref (@$attrs) {
+       push @col_arr, $ref->{COLUMN_NAME};
+    }
+
+    $sth->finish();
+
+    my $cols = join (', ', @col_arr);
+    $cols || die "unable to fetch column definitions: ERROR";
+
+    print $ofh "COPY $table ($cols) FROM stdin;\n";
+
+    my $cmd = "COPY $table ($cols) TO STDOUT";
+    $dbh->do($cmd);
+
+    my $data = '';
+    while ($dbh->pg_getcopydata($data) >= 0) {
+       print $ofh $data;
+    }
+
+    print $ofh "\\.\n\n";
+
+    if ($seq && $seqcol) {
+       print $ofh "SELECT setval('$seq', max($seqcol)) FROM $table;\n\n";
+    }
+}
+
+sub dumpdb {
+    my ($ofh) = @_;
+
+    print $ofh "SET client_encoding = 'SQL_ASCII';\n";
+    print $ofh "SET check_function_bodies = false;\n\n";
+
+    my $dbh = PMG::DBTools::open_ruledb();
+
+    print $ofh "BEGIN TRANSACTION;\n\n";
+
+    eval {
+       $dbh->begin_work;
+
+       # read a consistent snapshot
+       $dbh->do("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+
+       dump_table($dbh, 'attribut', $ofh);
+       dump_table($dbh, 'object', $ofh, 'object_id_seq', 'id');
+       dump_table($dbh, 'objectgroup', $ofh, 'objectgroup_id_seq', 'id');
+       dump_table($dbh, 'rule', $ofh, 'rule_id_seq', 'id');
+       dump_table($dbh, 'rulegroup', $ofh);
+       dump_table($dbh, 'userprefs', $ofh);
+
+       # we do not save the following tables: cgreylist, cmailstore, cmsreceivers, clusterinfo
+    };
+    my $err = $@;
+
+    $dbh->rollback(); # end read-only transaction
+
+    $dbh->disconnect();
+
+    die $err if $err;
+
+    print $ofh "COMMIT TRANSACTION;\n\n";
+}
+
+sub dumpstatdb {
+    my ($ofh) = @_;
+
+    print $ofh "SET client_encoding = 'SQL_ASCII';\n";
+    print $ofh "SET check_function_bodies = false;\n\n";
+
+    my $dbh = PMG::DBTools::open_ruledb();
+
+    eval {
+       $dbh->begin_work;
+
+       # read a consistent snapshot
+       $dbh->do("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+
+       print $ofh "BEGIN TRANSACTION;\n\n";
+
+       dump_table($dbh, 'dailystat', $ofh);
+       dump_table($dbh, 'domainstat', $ofh);
+       dump_table($dbh, 'virusinfo', $ofh);
+       dump_table($dbh, 'localstat', $ofh);
+
+       # drop/create the index is a little bit faster (20%)
+
+       print $ofh "DROP INDEX cstatistic_time_index;\n\n";
+       print $ofh "ALTER TABLE cstatistic DROP CONSTRAINT cstatistic_id_key;\n\n";
+       print $ofh "ALTER TABLE cstatistic DROP CONSTRAINT cstatistic_pkey;\n\n";
+       dump_table($dbh, 'cstatistic', $ofh, 'cstatistic_id_seq', 'id');
+       print $ofh "ALTER TABLE ONLY cstatistic ADD CONSTRAINT cstatistic_pkey PRIMARY KEY (cid, rid);\n\n";
+       print $ofh "ALTER TABLE ONLY cstatistic ADD CONSTRAINT cstatistic_id_key UNIQUE (id);\n\n";
+       print $ofh "CREATE INDEX CStatistic_Time_Index ON CStatistic (Time);\n\n";
+
+       print $ofh "DROP INDEX CStatistic_ID_Index;\n\n";
+       dump_table($dbh, 'creceivers', $ofh);
+       print $ofh "CREATE INDEX CStatistic_ID_Index ON CReceivers (CStatistic_CID, CStatistic_RID);\n\n";
+
+       dump_table($dbh, 'statinfo', $ofh);
+
+       print $ofh "COMMIT TRANSACTION;\n\n";
+    };
+    my $err = $@;
+
+    $dbh->rollback(); # end read-only transaction
+
+    $dbh->disconnect();
+
+    die $err if $err;
+}
+
+sub pmg_backup {
+    my ($filename, $include_statistics) = @_;
+
+    my $time = time;
+    my $dirname = "/tmp/proxbackup_$$.$time";
+    my $dbfn = "Proxmox_ruledb.sql";
+    my $statfn = "Proxmox_statdb.sql";
+    my $tarfn = "config_backup.tar";
+    my $sigfn = "proxmox_backup_v1.md5";
+    my $verfn = "version.txt";
+
+    eval {
+
+       my $targetdir = dirname($filename);
+       mkdir $targetdir; # try to create target dir
+       -d $targetdir ||
+           "unable to access target directory '$targetdir'\n";
+
+       # create a temporary directory
+       mkdir $dirname;
+
+       # dump the database first
+       my $fh = PMG::AtomicFile->open("$dirname/$dbfn", "w") ||
+           die "cant open '$dirname/$dbfn' - $! :ERROR";
+
+       dumpdb($fh);
+
+       $fh->close(1);
+
+       if ($include_statistics) {
+           # dump the statistic db
+           my $sfh = PMG::AtomicFile->open("$dirname/$statfn", "w") ||
+               die "cant open '$dirname/$statfn' - $! :ERROR";
+
+           dumpstatdb($sfh);
+
+           $sfh->close(1);
+       }
+
+       my $pkg = PMG::pmgcfg::package();
+       my $ver = PMG::pmgcfg::version();
+
+       my $vfh = PMG::AtomicFile->open ("$dirname/$verfn", "w") ||
+           die "cant open '$dirname/$verfn' - $! :ERROR";
+
+       $time = time;
+       my $now = localtime;
+       print $vfh "product: $pkg\nversion: $ver\nbackuptime:$time:$now\n";
+       $vfh->close(1);
+
+       my $sshfiles = -d '/root/.ssh' ? '/root/.ssh' : '';
+
+       my $extra_cfgs = '/etc/passwd /etc/group';
+
+       my $extra_fn = '/etc/shadow';
+       $extra_cfgs .= " $extra_fn" if -e $extra_fn;
+
+       $extra_fn = '/etc/gshadow';
+       $extra_cfgs .= " $extra_fn" if -e $extra_fn;
+
+       $extra_fn = '/etc/mail/spamassassin/custom.cf';
+       $extra_cfgs .= " $extra_fn" if -e $extra_fn;
+
+       #$extra_fn = '/etc/postfix/tls_policy';
+       #$extra_cfgs .= " $extra_fn" if -e $extra_fn;
+
+       my $extradb = $include_statistics ? $statfn : '';
+
+       # we do not store cluster configurations (cluster.cfg)
+
+       system("/bin/tar cf $dirname/$tarfn -C / " .
+              "/etc/pmg $sshfiles $extra_cfgs>/dev/null 2>&1") == 0 ||
+              die "unable to create system configuration backup: ERROR";
+
+       system("cd $dirname; md5sum $tarfn $dbfn $extradb $verfn> $sigfn") == 0 ||
+           die "unable to create backup signature: ERROR";
+
+       system("rm -f $filename; tar czf $filename -C $dirname $verfn $sigfn $dbfn $extradb $tarfn") == 0 ||
+           die "unable to create backup archive: ERROR";
+    };
+    my $err = $@;
+
+    rmtree $dirname;
+
+    if ($err) {
+       unlink $filename;
+       die $err;
+    }
+}
+
+1;