* [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration
@ 2020-10-28 18:54 Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
` (16 more replies)
0 siblings, 17 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
changes RFC->v1:
* moved the potentially reusable parts to pve-common (PBSTools.pm, and 2
functions in Systemd.pm)
* added GUI support (mostly adapted from the LDAPConfig) - huge thanks to
Dominik for his patience and help!
* added support for encrypted backups
* added support for pruning backups
* added a systemd-timer, which runs a daily prune based on the configured
settings
differences from the PBS storage-plugin config in PVE:
* the keep-options are kept as separate options instead of using a property
string for all (like they are in the datastore config in PBS)
cover-letter from the RFC:
This series is a minimal proof-of-concept for integrating PBS into PMG.
The code needs quite a bit of cleanup, and more testing, however I'll send it
as RFC, for an initial sanity-check - to see if I missed some needed
functionality.
It reuses quite a bit of code from pve-storage (PBSPlugin, and the systemd
unit manipulation from the PVE::API2::Disks::Directory) and I'd like to
refactor the common parts into pve-common.
The backup consists of the same files, that end up in a locally generated
PMG Backup (instead of creating a tar.gz we send the data to a PBS instance).
What works:
* creating and manipulating 'remotes' (PBS instances where we back up to)
* creating, restoring and forgetting backups to those remotes
* restoring a backup from another PMG installation from a configured remote
* creating schedules for backups (systemd-timer units) for periodic backups
via CLI (pmgbackup, pmgsh)
What's missing:
* quite a bit of refactoring (e.g. move the systemd-timer helpers and most of
PBSTools to pve-common (and reuse it in the PBS Storage plugin)
* encryption support
* pruneing support
* testing
* GUI
* Documentation
The first patch is a small cosmetic cleanup, and independent of the series.
A sample session (which I used for testing:
```
pmgbackup remote add pbs1 --datastore local --server 192.0.2.11 --user root@pam --password xxx --fingerprint ff:ff:...
pmgbackup remote list
pmgbackup pbsjob run pbs1
pmgbackup pbsjob restore pbs1 -althost pmg-live -config 1 -database 1 -statistic 1
pmgbackup pbsjob run pbs1
pmgbackup pbsjob list_backups pbs1
pmgbackup pbsjob create pbs1 --schedule 'minutely' --delay '0s'
systemctl list-timers --all
```
pve-common:
Stoiko Ivanov (2):
Systemd: add helpers for parsing unit files
add helper module for handling PBS Integration
src/Makefile | 1 +
src/PVE/PBSTools.pm | 309 ++++++++++++++++++++++++++++++++++++++++++++
src/PVE/Systemd.pm | 72 +++++++++++
3 files changed, 382 insertions(+)
create mode 100644 src/PVE/PBSTools.pm
pmg-api:
Stoiko Ivanov (11):
drop left-over commented out code
Backup: split backup creation and creating tar
Restore: optionally restore from directory
Backup: push restore options to PMG::Backup
debian: add dependency on proxmox-backup-client
add initial SectionConfig for pbs
Add API2 module for PBS configuration
Add API2 module for per-node backups to PBS
pbs-integration: add CLI calls to pmgbackup
add scheduled backup to PBS remotes
add daily timer for pruning configured remotes
debian/control | 1 +
debian/dirs | 1 +
debian/pmg-pbsbackup@.service | 6 +
debian/pmg-pbsprune.service | 6 +
debian/pmg-pbsprune.timer | 10 +
debian/rules | 4 +-
src/Makefile | 8 +-
src/PMG/API2/Backup.pm | 21 +-
src/PMG/API2/Config.pm | 7 +
src/PMG/API2/Nodes.pm | 7 +
src/PMG/API2/PBS/Job.pm | 544 ++++++++++++++++++++++++++++++++++
src/PMG/API2/PBS/Remote.pm | 248 ++++++++++++++++
src/PMG/Backup.pm | 86 +++++-
src/PMG/CLI/pmgbackup.pm | 102 +++++++
src/PMG/PBSConfig.pm | 209 +++++++++++++
src/PMG/PBSSchedule.pm | 104 +++++++
16 files changed, 1327 insertions(+), 37 deletions(-)
create mode 100644 debian/pmg-pbsbackup@.service
create mode 100644 debian/pmg-pbsprune.service
create mode 100644 debian/pmg-pbsprune.timer
create mode 100644 src/PMG/API2/PBS/Job.pm
create mode 100644 src/PMG/API2/PBS/Remote.pm
create mode 100644 src/PMG/PBSConfig.pm
create mode 100644 src/PMG/PBSSchedule.pm
pmg-gui:
Stoiko Ivanov (3):
Make Backup/Restore panel a menuentry
refactor RestoreWindow for PBS
add PBSConfig tab to Backup menu
js/BackupConfiguration.js | 23 ++
js/BackupRestore.js | 68 ++--
js/Makefile | 2 +
js/NavigationTree.js | 6 +
js/PBSConfig.js | 680 ++++++++++++++++++++++++++++++++++++++
js/SystemConfiguration.js | 4 -
6 files changed, 750 insertions(+), 33 deletions(-)
create mode 100644 js/BackupConfiguration.js
create mode 100644 js/PBSConfig.js
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 8:26 ` [pmg-devel] applied: " Dietmar Maurer
2020-11-10 8:34 ` [pmg-devel] " Thomas Lamprecht
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 2/2] add helper module for handling PBS Integration Stoiko Ivanov
` (15 subsequent siblings)
16 siblings, 2 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
taken from pve-storage/PVE/API2/Disks/Directory.pm (and made available as
public sub)
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
refactoring pve-storage to use this (and drop the now duplicated code) will
be done separately
src/PVE/Systemd.pm | 72 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/src/PVE/Systemd.pm b/src/PVE/Systemd.pm
index 85b35a3..bcba5eb 100644
--- a/src/PVE/Systemd.pm
+++ b/src/PVE/Systemd.pm
@@ -7,6 +7,8 @@ use Net::DBus qw(dbus_uint32 dbus_uint64);
use Net::DBus::Callback;
use Net::DBus::Reactor;
+use PVE::Tools qw(file_set_contents file_get_contents trim);
+
sub escape_unit {
my ($val, $is_path) = @_;
@@ -163,4 +165,74 @@ sub wait_for_unit_removed($;$) {
}, $timeout);
}
+sub read_ini {
+ my ($filename) = @_;
+
+ my $content = file_get_contents($filename);
+ my @lines = split /\n/, $content;
+
+ my $result = {};
+ my $section;
+
+ foreach my $line (@lines) {
+ $line = trim($line);
+ if ($line =~ m/^\[([^\]]+)\]/) {
+ $section = $1;
+ if (!defined($result->{$section})) {
+ $result->{$section} = {};
+ }
+ } elsif ($line =~ m/^(.*?)=(.*)$/) {
+ my ($key, $val) = ($1, $2);
+ if (!$section) {
+ warn "key value pair found without section, skipping\n";
+ next;
+ }
+
+ if ($result->{$section}->{$key}) {
+ # make duplicate properties to arrays to keep the order
+ my $prop = $result->{$section}->{$key};
+ if (ref($prop) eq 'ARRAY') {
+ push @$prop, $val;
+ } else {
+ $result->{$section}->{$key} = [$prop, $val];
+ }
+ } else {
+ $result->{$section}->{$key} = $val;
+ }
+ }
+ # ignore everything else
+ }
+
+ return $result;
+};
+
+sub write_ini {
+ my ($ini, $filename) = @_;
+
+ my $content = "";
+
+ foreach my $sname (sort keys %$ini) {
+ my $section = $ini->{$sname};
+
+ $content .= "[$sname]\n";
+
+ foreach my $pname (sort keys %$section) {
+ my $prop = $section->{$pname};
+
+ if (!ref($prop)) {
+ $content .= "$pname=$prop\n";
+ } elsif (ref($prop) eq 'ARRAY') {
+ foreach my $val (@$prop) {
+ $content .= "$pname=$val\n";
+ }
+ } else {
+ die "invalid property '$pname'\n";
+ }
+ }
+ $content .= "\n";
+ }
+
+ file_set_contents($filename, $content);
+};
+
1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pve-common 2/2] add helper module for handling PBS Integration
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 01/11] drop left-over commented out code Stoiko Ivanov
` (14 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
PBSTools.pm contains methods which eventually should be shared between
PVE and PMG, for:
* handling (sensitive) config-information (passwords, encryption keys)
* creating/restoring/forgetting/listing backups
code is mostly based on the current PBSPlugin in pve-storage
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/Makefile | 1 +
src/PVE/PBSTools.pm | 309 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 310 insertions(+)
create mode 100644 src/PVE/PBSTools.pm
diff --git a/src/Makefile b/src/Makefile
index 1987d0e..9084903 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -20,6 +20,7 @@ LIB_SOURCES = \
LDAP.pm \
Network.pm \
OTP.pm \
+ PBSTools.pm \
PTY.pm \
ProcFSTools.pm \
RESTEnvironment.pm \
diff --git a/src/PVE/PBSTools.pm b/src/PVE/PBSTools.pm
new file mode 100644
index 0000000..77cc30b
--- /dev/null
+++ b/src/PVE/PBSTools.pm
@@ -0,0 +1,309 @@
+package PVE::PBSTools;
+
+# utility functions for interaction with Proxmox Backup Server
+
+use strict;
+use warnings;
+use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
+use IO::File;
+use JSON;
+use POSIX qw(strftime ENOENT);
+
+use PVE::Tools qw(run_command file_set_contents file_get_contents file_read_firstline);
+use PVE::JSONSchema qw(get_standard_option);
+
+# Helpers
+my $secret_dir;
+
+sub set_secret_dir {
+ my ($dir) = @_;
+ $secret_dir = $dir;
+}
+
+sub pbs_password_file_name {
+ my ($storeid) = @_;
+
+ return "${secret_dir}/${storeid}.pw";
+}
+
+sub pbs_set_password {
+ my ($storeid, $password) = @_;
+
+ my $pwfile = pbs_password_file_name($storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($pwfile, "$password\n", 0600);
+}
+
+sub pbs_delete_password {
+ my ($storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($storeid);
+
+ unlink $pwfile;
+}
+
+sub pbs_get_password {
+ my ($storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($storeid);
+
+ return PVE::Tools::file_read_firstline($pwfile);
+}
+
+sub pbs_encryption_key_file_name {
+ my ($storeid) = @_;
+
+ return "${secret_dir}/${storeid}.enc";
+}
+
+sub pbs_set_encryption_key {
+ my ($storeid, $key) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($encfile, "$key\n", 0600);
+}
+
+sub pbs_delete_encryption_key {
+ my ($storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($storeid);
+
+ if (!unlink $encfile) {
+ return if $! == ENOENT;
+ die "failed to delete encryption key! $!\n";
+ }
+}
+
+sub pbs_get_encryption_key {
+ my ($storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($storeid);
+
+ return PVE::Tools::file_get_contents($encfile);
+}
+
+# Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
+sub pbs_open_encryption_key {
+ my ($storeid) = @_;
+
+ my $encryption_key_file = pbs_encryption_key_file_name($storeid);
+
+ my $keyfd;
+ if (!open($keyfd, '<', $encryption_key_file)) {
+ return undef if $! == ENOENT;
+ die "failed to open encryption key: $encryption_key_file: $!\n";
+ }
+
+ return $keyfd;
+}
+
+my $USE_CRYPT_PARAMS = {
+ backup => 1,
+ restore => 1,
+ 'upload-log' => 1,
+};
+
+my sub do_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+
+ my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
+
+ my $client_exe = '/usr/bin/proxmox-backup-client';
+ die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
+ if ! -x $client_exe;
+
+ my $server = $scfg->{server};
+ my $datastore = $scfg->{datastore};
+ my $username = $scfg->{username} // 'root@pam';
+
+ my $userns_cmd = delete $opts{userns_cmd};
+
+ my $cmd = [];
+
+ push @$cmd, @$userns_cmd if defined($userns_cmd);
+
+ push @$cmd, $client_exe, $client_cmd;
+
+ # This must live in the top scope to not get closed before the `run_command`
+ my $keyfd;
+ if ($use_crypto) {
+ if (defined($keyfd = pbs_open_encryption_key($storeid))) {
+ my $flags = fcntl($keyfd, F_GETFD, 0)
+ // die "failed to get file descriptor flags: $!\n";
+ fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
+ or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
+ push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
+ } else {
+ push @$cmd, '--crypt-mode=none';
+ }
+ }
+
+ push @$cmd, @$param if defined($param);
+
+ push @$cmd, "--repository", "$username\@$server:$datastore";
+
+ local $ENV{PBS_PASSWORD} = pbs_get_password($storeid);
+
+ local $ENV{PBS_FINGERPRINT} = $scfg->{fingerprint};
+
+ # no ascii-art on task logs
+ local $ENV{PROXMOX_OUTPUT_NO_BORDER} = 1;
+ local $ENV{PROXMOX_OUTPUT_NO_HEADER} = 1;
+
+ if (my $logfunc = $opts{logfunc}) {
+ $logfunc->("run: " . join(' ', @$cmd));
+ }
+
+ run_command($cmd, %opts);
+}
+
+my sub run_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+ return do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, %opts);
+}
+
+my sub run_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_;
+
+ my $json_str = '';
+ my $outfunc = sub { $json_str .= "$_[0]\n" };
+
+ $param = [] if !defined($param);
+ $param = [ $param ] if !ref($param);
+
+ $param = [@$param, '--output-format=json'] if !$no_output;
+
+ do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
+ outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
+
+ return undef if $no_output;
+
+ my $res = decode_json($json_str);
+
+ return $res;
+}
+
+sub autogen_encryption_key {
+ my ($storeid) = @_;
+ my $encfile = pbs_encryption_key_file_name($storeid);
+ run_command(['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile]);
+};
+
+sub get_snapshots {
+ my ($scfg, $remote, $opts) = @_;
+
+ my $param = [];
+ if (defined($opts->{group})) {
+ push @$param, $opts->{group};
+ }
+
+ return run_client_cmd($scfg, $remote, "snapshots", $param);
+}
+
+sub backup_tree {
+ my ($scfg, $remote, $opts) = @_;
+
+ my $type = delete $opts->{type};
+ die "backup-type not provided\n" if !defined($type);
+ my $id = delete $opts->{id};
+ die "backup-id not provided\n" if !defined($id);
+ my $root = delete $opts->{root};
+ die "root dir not provided\n" if !defined($root);
+ my $pxarname = delete $opts->{pxarname};
+ die "archive name not provided\n" if !defined($pxarname);
+ my $time = delete $opts->{time};
+
+ my $param = [];
+
+ push @$param, "$pxarname.pxar:$root";
+ push @$param, '--backup-type', $type;
+ push @$param, '--backup-id', $id;
+ push @$param, '--backup-time', $time if defined($time);
+
+ return run_raw_client_cmd($scfg, $remote, 'backup', $param, %$opts);
+}
+
+sub restore_pxar {
+ my ($scfg, $remote, $opts) = @_;
+
+ my $snapshot = delete $opts->{snapshot};
+ die "snapshot not provided\n" if !defined($snapshot);
+ my $pxarname = delete $opts->{pxarname};
+ die "archive name not provided\n" if !defined($pxarname);
+ my $target = delete $opts->{target};
+ die "restore-target not provided\n" if !defined($target);
+ #my $time = delete $opts->{time};
+
+ my $param = [];
+
+ push @$param, "$snapshot";
+ push @$param, "$pxarname.pxar";
+ push @$param, "$target";
+ push @$param, "--allow-existing-dirs", 0;
+
+ return run_raw_client_cmd($scfg, $remote, 'restore', $param, %$opts);
+}
+
+sub forget_snapshot {
+ my ($scfg, $remote, $snapshot) = @_;
+
+ die "snapshot not provided\n" if !defined($snapshot);
+
+ my $param = [];
+
+ push @$param, "$snapshot";
+
+ return run_raw_client_cmd($scfg, $remote, 'forget', $param);
+}
+
+sub prune_group {
+ my ($scfg, $remote, $opts, $prune_opts, $group) = @_;
+
+ die "group not provided\n" if !defined($group);
+
+ # do nothing if no keep options specified for remote
+ return [] if scalar(keys %$prune_opts) == 0;
+
+ my $param = [];
+
+ push @$param, "--quiet";
+
+ if (defined($opts->{'dry-run'}) && $opts->{'dry-run'}) {
+ push @$param, "--dry-run", $opts->{'dry-run'};
+ }
+
+ foreach my $keep_opt (keys %$prune_opts) {
+ push @$param, "--$keep_opt", $prune_opts->{$keep_opt};
+ }
+ push @$param, "$group";
+
+ return run_client_cmd($scfg, $remote, 'prune', $param);
+}
+
+sub status {
+ my ($class, $storeid, $scfg) = @_;
+
+ my $total = 0;
+ my $free = 0;
+ my $used = 0;
+ my $active = 0;
+
+ eval {
+ my $res = run_client_cmd($scfg, $storeid, "status");
+
+ $active = 1;
+ $total = $res->{total};
+ $used = $res->{used};
+ $free = $res->{avail};
+ };
+ if (my $err = $@) {
+ warn $err;
+ }
+
+ return ($total, $free, $used, $active);
+}
+
+1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 01/11] drop left-over commented out code
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 2/2] add helper module for handling PBS Integration Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 5:58 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 02/11] Backup: split backup creation and creating tar Stoiko Ivanov
` (13 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/Backup.pm | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm
index 11eff5f..025bac2 100644
--- a/src/PMG/Backup.pm
+++ b/src/PMG/Backup.pm
@@ -186,8 +186,6 @@ sub pmg_backup {
push @$extra_cfgs, $sa_custom_config_fn;
- #push @$extra_cfgs, '/etc/postfix/tls_policy';
-
my $extradb = $include_statistics ? $statfn : '';
my $extra = join(' ', @$extra_cfgs);
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 02/11] Backup: split backup creation and creating tar
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (2 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 01/11] drop left-over commented out code Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 6:20 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 03/11] Restore: optionally restore from directory Stoiko Ivanov
` (12 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
In preparation for integrating PMG with PBS split the current creation of
a PMG backup into 2 methods:
* create all files in a backup in a target directory
* create a tarball from a backup in a temporary directory
use the changed method in the backup API call.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/Backup.pm | 2 +-
src/PMG/Backup.pm | 41 ++++++++++++++++++++++++++++++++---------
2 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/src/PMG/API2/Backup.pm b/src/PMG/API2/Backup.pm
index 0bfcfc9..08c06b5 100644
--- a/src/PMG/API2/Backup.pm
+++ b/src/PMG/API2/Backup.pm
@@ -131,7 +131,7 @@ __PACKAGE__->register_method ({
print "starting backup to: $filename\n";
- PMG::Backup::pmg_backup($filename, $param->{statistic});
+ PMG::Backup::pmg_backup_pack($filename, $param->{statistic});
print "backup finished\n";
diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm
index 025bac2..40c41f3 100644
--- a/src/PMG/Backup.pm
+++ b/src/PMG/Backup.pm
@@ -133,10 +133,11 @@ sub dumpstatdb {
}
sub pmg_backup {
- my ($filename, $include_statistics) = @_;
+ my ($dirname, $include_statistics) = @_;
+
+ die "No backupdir provided!\n" if !defined($dirname);
my $time = time;
- my $dirname = "/tmp/proxbackup_$$.$time";
my $dbfn = "Proxmox_ruledb.sql";
my $statfn = "Proxmox_statdb.sql";
my $tarfn = "config_backup.tar";
@@ -145,12 +146,7 @@ sub pmg_backup {
eval {
- my $targetdir = dirname($filename);
- mkdir $targetdir; # try to create target dir
- -d $targetdir ||
- die "unable to access target directory '$targetdir'\n";
-
- # create a temporary directory
+ # create backup directory
mkdir $dirname;
# dump the database first
@@ -197,7 +193,34 @@ sub pmg_backup {
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 ||
+ };
+ my $err = $@;
+
+ if ($err) {
+ die $err;
+ }
+}
+
+sub pmg_backup_pack {
+ my ($filename, $include_statistics) = @_;
+
+ my $time = time;
+ my $dirname = "/tmp/proxbackup_$$.$time";
+
+ eval {
+
+ my $targetdir = dirname($filename);
+ mkdir $targetdir; # try to create target dir
+ -d $targetdir ||
+ die "unable to access target directory '$targetdir'\n";
+
+ rmtree $dirname;
+ # create backup directory
+ mkdir $dirname;
+
+ pmg_backup($dirname, $include_statistics);
+
+ system("rm -f $filename; tar czf $filename --strip-components=1 -C $dirname .") == 0 ||
die "unable to create backup archive: ERROR";
};
my $err = $@;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 03/11] Restore: optionally restore from directory
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (3 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 02/11] Backup: split backup creation and creating tar Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 6:26 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup Stoiko Ivanov
` (11 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
In preparation for integrating PMG with PBS decide based on the type of the
provided filename, whether or not to untar:
* if it's a directory skip untarring (PBS)
* if it's a filename untar (local backup)
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/Backup.pm | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm
index 40c41f3..aec7905 100644
--- a/src/PMG/Backup.pm
+++ b/src/PMG/Backup.pm
@@ -245,12 +245,23 @@ sub pmg_restore {
my $tarfn = "config_backup.tar";
my $sigfn = "proxmox_backup_v1.md5";
+ my $untar = 1;
+
+ # directory indicates that the files were restored from a PBS remote
+ if ( -d $filename ) {
+ $dirname = $filename;
+ $untar = 0;
+ }
+
eval {
- # create a temporary directory
- mkdir $dirname;
- system("cd $dirname; tar xzf $filename >/dev/null 2>&1") == 0 ||
- die "unable to extract backup archive: ERROR";
+ if ($untar) {
+ # create a temporary directory
+ mkdir $dirname;
+
+ system("cd $dirname; tar xzf $filename >/dev/null 2>&1") == 0 ||
+ die "unable to extract backup archive: ERROR";
+ }
system("cd $dirname; md5sum -c $sigfn") == 0 ||
die "proxmox backup signature check failed: ERROR";
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (4 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 03/11] Restore: optionally restore from directory Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 6:32 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client Stoiko Ivanov
` (10 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
that way they can be reused for the PBS restore API call
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/Backup.pm | 19 +------------------
src/PMG/Backup.pm | 24 ++++++++++++++++++++++++
2 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/src/PMG/API2/Backup.pm b/src/PMG/API2/Backup.pm
index 08c06b5..4ea28d1 100644
--- a/src/PMG/API2/Backup.pm
+++ b/src/PMG/API2/Backup.pm
@@ -202,26 +202,9 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
+ PMG::Backup::get_restore_options(),
node => get_standard_option('pve-node'),
filename => $backup_filename_property,
- config => {
- description => "Restore system configuration.",
- type => 'boolean',
- optional => 1,
- default => 0,
- },
- database => {
- description => "Restore the rule database. This is the default.",
- type => 'boolean',
- optional => 1,
- default => 1,
- },
- statistic => {
- description => "Restore statistic databases. Only considered when you restore the 'database'.",
- type => 'boolean',
- optional => 1,
- default => 0,
- },
},
},
returns => { type => "string" },
diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm
index aec7905..5e11684 100644
--- a/src/PMG/Backup.pm
+++ b/src/PMG/Backup.pm
@@ -6,6 +6,7 @@ use Data::Dumper;
use File::Basename;
use File::Path;
+use PVE::JSONSchema qw(get_standard_option);
use PVE::Tools;
use PMG::pmgcfg;
@@ -14,6 +15,29 @@ use PMG::Utils qw(postgres_admin_cmd);
my $sa_custom_config_fn = "/etc/mail/spamassassin/custom.cf";
+sub get_restore_options {
+ return (
+ node => get_standard_option('pve-node'),
+ config => {
+ description => "Restore system configuration.",
+ type => 'boolean',
+ optional => 1,
+ default => 0,
+ },
+ database => {
+ description => "Restore the rule database. This is the default.",
+ type => 'boolean',
+ optional => 1,
+ default => 1,
+ },
+ statistic => {
+ description => "Restore statistic databases. Only considered when you restore the 'database'.",
+ type => 'boolean',
+ optional => 1,
+ default => 0,
+ });
+}
+
sub dump_table {
my ($dbh, $table, $ofh, $seq, $seqcol) = @_;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (5 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 6:33 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs Stoiko Ivanov
` (9 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
debian/control | 1 +
1 file changed, 1 insertion(+)
diff --git a/debian/control b/debian/control
index af10bc0..2460411 100644
--- a/debian/control
+++ b/debian/control
@@ -76,6 +76,7 @@ Depends: apt,
pmg-log-tracker,
postfix (>= 2.5.5),
postgresql-11,
+ proxmox-backup-client,,
proxmox-mini-journalreader,
proxmox-spamassassin,
pve-xtermjs (>= 1.0-1),
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (6 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 6:38 ` Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 07/11] Add API2 module for PBS configuration Stoiko Ivanov
` (8 subsequent siblings)
16 siblings, 1 reply; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
add a SectionConfig definition to hold information about PBS-remotes used
for backing up PMG.
Mostly adapted from the PBSPlugin.pm in pve-storage.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
This commit needs a versioned dependency on pve-common
debian/dirs | 1 +
src/Makefile | 1 +
src/PMG/PBSConfig.pm | 209 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 211 insertions(+)
create mode 100644 src/PMG/PBSConfig.pm
diff --git a/debian/dirs b/debian/dirs
index f7ac2e7..f138bb4 100644
--- a/debian/dirs
+++ b/debian/dirs
@@ -1,4 +1,5 @@
/etc/pmg
/etc/pmg/dkim
+/etc/pmg/pbs
/var/lib/pmg
/var/lib/pmg/backup
diff --git a/src/Makefile b/src/Makefile
index 05d9598..daa9d46 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -66,6 +66,7 @@ LIBSOURCES = \
PMG/SMTP.pm \
PMG/Unpack.pm \
PMG/Backup.pm \
+ PMG/PBSConfig.pm \
PMG/RuleCache.pm \
PMG/Statistic.pm \
PMG/UserConfig.pm \
diff --git a/src/PMG/PBSConfig.pm b/src/PMG/PBSConfig.pm
new file mode 100644
index 0000000..768f36c
--- /dev/null
+++ b/src/PMG/PBSConfig.pm
@@ -0,0 +1,209 @@
+package PMG::PBSConfig;
+
+# section config implementation for PBS integration in PMG
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(extract_param);
+use PVE::SectionConfig;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::PBSTools;
+
+use base qw(PVE::SectionConfig);
+
+my $inotify_file_id = 'pmg-pbs.conf';
+my $secret_dir = '/etc/pmg/pbs';
+my $config_filename = "${secret_dir}/pbs.conf";
+
+
+my %prune_option = (
+ optional => 1,
+ type => 'integer', minimum => '0',
+ format_description => 'N',
+);
+
+my %prune_properties = (
+ 'keep-last' => {
+ %prune_option,
+ description => 'Keep the last <N> backups.',
+ },
+ 'keep-hourly' => {
+ %prune_option,
+ description => 'Keep backups for the last <N> different hours. If there is more' .
+ 'than one backup for a single hour, only the latest one is kept.'
+ },
+ 'keep-daily' => {
+ %prune_option,
+ description => 'Keep backups for the last <N> different days. If there is more' .
+ 'than one backup for a single day, only the latest one is kept.'
+ },
+ 'keep-weekly' => {
+ %prune_option,
+ description => 'Keep backups for the last <N> different weeks. If there is more' .
+ 'than one backup for a single week, only the latest one is kept.'
+ },
+ 'keep-monthly' => {
+ %prune_option,
+ description => 'Keep backups for the last <N> different months. If there is more' .
+ 'than one backup for a single month, only the latest one is kept.'
+ },
+ 'keep-yearly' => {
+ %prune_option,
+ description => 'Keep backups for the last <N> different years. If there is more' .
+ 'than one backup for a single year, only the latest one is kept.'
+ },
+);
+
+my $defaultData = {
+ propertyList => {
+ type => { description => "Section type." },
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+};
+
+sub properties {
+ return {
+ datastore => {
+ description => "Proxmox backup server datastore name.",
+ type => 'string',
+ },
+ server => {
+ description => "Proxmox backup server address.",
+ type => 'string', format => 'address',
+ maxLength => 256,
+ },
+ disable => {
+ description => "Flag to disable/deactivate the entry.",
+ type => 'boolean',
+ optional => 1,
+ },
+ password => {
+ description => "Password for the user on the Proxmox backup server.",
+ type => 'string',
+ optional => 1,
+ },
+ username => get_standard_option('pmg-email-address', {
+ description => "Username on the Proxmox backup server"
+ }),
+ fingerprint => get_standard_option('fingerprint-sha256'),
+ 'encryption-key' => {
+ description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.",
+ type => 'string',
+ optional => 1,
+ },
+ %prune_properties,
+ };
+}
+
+sub options {
+ return {
+ server => {},
+ datastore => {},
+ disable => { optional => 1 },
+ username => { optional => 1 },
+ password => { optional => 1 },
+ 'encryption-key' => { optional => 1 },
+ fingerprint => { optional => 1 },
+ 'keep-last' => { optional => 1 },
+ 'keep-hourly' => { optional => 1 },
+ 'keep-daily' => { optional => 1 },
+ 'keep-weekly' => { optional => 1 },
+ 'keep-monthly' => { optional => 1 },
+ 'keep-yearly' => { optional => 1 },
+ };
+}
+
+sub type {
+ return 'pbs';
+}
+
+sub private {
+ return $defaultData;
+}
+
+sub prune_options {
+ my ($self, $remote) = @_;
+
+ my $remote_cfg = $self->{ids}->{$remote};
+
+ my $res = {};
+
+ foreach my $keep_opt (keys %prune_properties) {
+
+ if (defined($remote_cfg->{$keep_opt})) {
+ $res->{$keep_opt} = $remote_cfg->{$keep_opt};
+ }
+ }
+ return $res;
+}
+
+sub parse_config {
+ my ($class, $filename, $raw) = @_;
+
+ my $cfg = $class->SUPER::parse_config($filename, $raw);
+
+ PVE::PBSTools::set_secret_dir($secret_dir);
+
+ return $cfg;
+}
+
+sub new {
+ my ($type) = @_;
+
+ my $class = ref($type) || $type;
+
+ my $cfg = PVE::INotify::read_file($inotify_file_id);
+
+ return bless $cfg, $class;
+}
+
+sub write {
+ my ($self) = @_;
+
+ PVE::INotify::write_file($inotify_file_id, $self);
+}
+
+my $lockfile = "/var/lock/pmgpbsconfig.lck";
+
+sub lock_config {
+ my ($code, $errmsg) = @_;
+
+ my $p = PVE::Tools::lock_file($lockfile, undef, $code);
+ if (my $err = $@) {
+ $errmsg ? die "$errmsg: $err" : die $err;
+ }
+}
+
+
+__PACKAGE__->register();
+__PACKAGE__->init();
+
+sub read_pmg_pbs_conf {
+ my ($filename, $fh) = @_;
+
+ local $/ = undef; # slurp mode
+
+ my $raw = defined($fh) ? <$fh> : '';
+
+ return __PACKAGE__->parse_config($filename, $raw);
+}
+
+sub write_pmg_pbs_conf {
+ my ($filename, $fh, $cfg) = @_;
+
+ my $raw = __PACKAGE__->write_config($filename, $cfg);
+
+ PVE::Tools::safe_print($filename, $fh, $raw);
+}
+
+PVE::INotify::register_file($inotify_file_id, $config_filename,
+ \&read_pmg_pbs_conf,
+ \&write_pmg_pbs_conf,
+ undef,
+ always_call_parser => 1);
+
+1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 07/11] Add API2 module for PBS configuration
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (7 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 08/11] Add API2 module for per-node backups to PBS Stoiko Ivanov
` (7 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
The module provides the API methods for creating/updating/listing/deleting
PBS remotes
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/Makefile | 1 +
src/PMG/API2/Config.pm | 7 ++
src/PMG/API2/PBS/Remote.pm | 248 +++++++++++++++++++++++++++++++++++++
3 files changed, 256 insertions(+)
create mode 100644 src/PMG/API2/PBS/Remote.pm
diff --git a/src/Makefile b/src/Makefile
index daa9d46..5add6af 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -137,6 +137,7 @@ LIBSOURCES = \
PMG/API2/Statistics.pm \
PMG/API2/MailTracker.pm \
PMG/API2/Backup.pm \
+ PMG/API2/PBS/Remote.pm \
PMG/API2/Nodes.pm \
PMG/API2/Postfix.pm \
PMG/API2/Quarantine.pm \
diff --git a/src/PMG/API2/Config.pm b/src/PMG/API2/Config.pm
index d4a9679..e11eb3f 100644
--- a/src/PMG/API2/Config.pm
+++ b/src/PMG/API2/Config.pm
@@ -25,6 +25,7 @@ use PMG::API2::Fetchmail;
use PMG::API2::DestinationTLSPolicy;
use PMG::API2::DKIMSign;
use PMG::API2::SACustom;
+use PMG::API2::PBS::Remote;
use base qw(PVE::RESTHandler);
@@ -93,6 +94,11 @@ __PACKAGE__->register_method({
path => 'customscores',
});
+__PACKAGE__->register_method ({
+ subclass => "PMG::API2::PBS::Remote",
+ path => 'pbs',
+});
+
__PACKAGE__->register_method ({
name => 'index',
path => '',
@@ -131,6 +137,7 @@ __PACKAGE__->register_method ({
push @$res, { section => 'regextest' };
push @$res, { section => 'tlspolicy' };
push @$res, { section => 'dkim' };
+ push @$res, { section => 'pbs' };
return $res;
}});
diff --git a/src/PMG/API2/PBS/Remote.pm b/src/PMG/API2/PBS/Remote.pm
new file mode 100644
index 0000000..1c1d93f
--- /dev/null
+++ b/src/PMG/API2/PBS/Remote.pm
@@ -0,0 +1,248 @@
+package PMG::API2::PBS::Remote;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+use PVE::PBSTools;
+
+use PMG::PBSConfig;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ name => 'list',
+ path => '',
+ method => 'GET',
+ description => "List all configured Proxmox Backup Server instances.",
+ permissions => { check => [ 'admin', 'audit' ] },
+ proxyto => 'master',
+ protected => 1,
+ parameters => {
+ additionalProperties => 0,
+ properties => {}
+ },
+ returns => {
+ type => "array",
+ items => PMG::PBSConfig->createSchema(1),
+ links => [ { rel => 'child', href => "{remote}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $res = [];
+
+ my $conf = PMG::PBSConfig->new();
+
+ if (defined($conf)) {
+ foreach my $remote (keys %{$conf->{ids}}) {
+ my $d = $conf->{ids}->{$remote};
+ my $entry = {
+ remote => $remote,
+ server => $d->{server},
+ datastore => $d->{datastore},
+ username => $d->{username},
+ disable => $d->{disable},
+ 'encryption-key' => $d->{'encryption-key'},
+ };
+ push @$res, $entry;
+ }
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ path => '',
+ method => 'POST',
+ description => "Add Proxmox Backup Server instance.",
+ permissions => { check => [ 'admin' ] },
+ proxyto => 'master',
+ protected => 1,
+ parameters => PMG::PBSConfig->createSchema(1),
+ returns => { type => 'null' } ,
+ code => sub {
+ my ($param) = @_;
+
+ my $code = sub {
+
+ my $conf = PMG::PBSConfig->new();
+ $conf->{ids} //= {};
+ my $ids = $conf->{ids};
+
+ my $remote = extract_param($param, 'remote');
+ die "PBS remote '$remote' already exists\n"
+ if $ids->{$remote};
+
+ my $remotecfg = PMG::PBSConfig->check_config($remote, $param, 1);
+
+ my $password = extract_param($remotecfg, 'password');
+ PVE::PBSTools::pbs_set_password($remote, $password) if defined($password);
+
+ if (defined(my $encryption_key = extract_param($remotecfg, 'encryption-key'))) {
+ if ($encryption_key eq 'autogen') {
+ PVE::PBSTools::autogen_encryption_key($remote);
+ } else {
+ PVE::PBSTools::pbs_set_encryption_key($remote, $encryption_key);
+ }
+ $remotecfg->{'encryption-key'} = 1;
+ }
+
+ $ids->{$remote} = $remotecfg;
+ $conf->write();
+ };
+
+ PMG::PBSConfig::lock_config($code, "add PBS remote failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read_config',
+ path => '{remote}',
+ method => 'GET',
+ description => "Get PBS remote configuration.",
+ proxyto => 'master',
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 1,
+ properties => {
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => {},
+ code => sub {
+ my ($param) = @_;
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote = $param->{remote};
+
+ my $data = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$data;
+
+ delete $data->{type};
+
+ $data->{digest} = $conf->{digest};
+ $data->{remote} = $remote;
+
+ return $data;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update_config',
+ path => '{remote}',
+ method => 'PUT',
+ description => "Update PBS remote settings.",
+ permissions => { check => [ 'admin' ] },
+ protected => 1,
+ proxyto => 'master',
+ parameters => PMG::PBSConfig->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $code = sub {
+
+ my $conf = PMG::PBSConfig->new();
+ my $ids = $conf->{ids};
+
+ my $digest = extract_param($param, 'digest');
+ PVE::SectionConfig::assert_if_modified($conf, $digest);
+
+ my $remote = extract_param($param, 'remote');
+
+ die "PBS remote '$remote' does not exist\n"
+ if !$ids->{$remote};
+
+ my $delete_str = extract_param($param, 'delete');
+ die "no options specified\n"
+ if !$delete_str && !scalar(keys %$param);
+
+ foreach my $opt (PVE::Tools::split_list($delete_str)) {
+ if ($opt eq 'password') {
+ PVE::PBSTools::pbs_delete_password($remote);
+ } elsif ($opt eq 'encryption-key') {
+ PVE::PBSTools::pbs_delete_encryption_key($remote);
+ }
+
+ delete $ids->{$remote}->{$opt};
+ }
+
+ if (defined(my $password = extract_param($param, 'password'))) {
+ PVE::PBSTools::pbs_set_password($remote, $password);
+ }
+ if (defined(my $encryption_key = extract_param($param, 'encryption-key'))) {
+ if ($encryption_key eq 'autogen') {
+ PVE::PBSTools::autogen_encryption_key($remote);
+ } else {
+ PVE::PBSTools::pbs_set_encryption_key($remote, $encryption_key);
+ }
+ $param->{'encryption-key'} = 1;
+ }
+
+ my $remoteconfig = PMG::PBSConfig->check_config($remote, $param, 0, 1);
+
+ foreach my $p (keys %$remoteconfig) {
+ $ids->{$remote}->{$p} = $remoteconfig->{$p};
+ }
+
+ $conf->write();
+ };
+
+ PMG::PBSConfig::lock_config($code, "update PBS remote failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ path => '{remote}',
+ method => 'DELETE',
+ description => "Delete an PBS remote",
+ permissions => { check => [ 'admin' ] },
+ protected => 1,
+ proxyto => 'master',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ remote => {
+ description => "Profile ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ }
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $code = sub {
+
+ my $conf = PMG::PBSConfig->new();
+ my $ids = $conf->{ids};
+
+ my $remote = $param->{remote};
+
+ die "PBS remote '$remote' does not exist\n"
+ if !$ids->{$remote};
+
+ PVE::PBSTools::pbs_delete_password($remote);
+ PVE::PBSTools::pbs_delete_encryption_key($remote);
+ delete $ids->{$remote};
+
+ $conf->write();
+ };
+
+ PMG::PBSConfig::lock_config($code, "delete PBS remote failed");
+
+ return undef;
+ }});
+
+1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 08/11] Add API2 module for per-node backups to PBS
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (8 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 07/11] Add API2 module for PBS configuration Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 09/11] pbs-integration: add CLI calls to pmgbackup Stoiko Ivanov
` (6 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
The module adds API2 methods for:
* creating/restoring/listing/forgetting backups on a configured PBS remote
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/Makefile | 1 +
src/PMG/API2/Nodes.pm | 7 +
src/PMG/API2/PBS/Job.pm | 413 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 421 insertions(+)
create mode 100644 src/PMG/API2/PBS/Job.pm
diff --git a/src/Makefile b/src/Makefile
index 5add6af..fb42f21 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -137,6 +137,7 @@ LIBSOURCES = \
PMG/API2/Statistics.pm \
PMG/API2/MailTracker.pm \
PMG/API2/Backup.pm \
+ PMG/API2/PBS/Job.pm \
PMG/API2/PBS/Remote.pm \
PMG/API2/Nodes.pm \
PMG/API2/Postfix.pm \
diff --git a/src/PMG/API2/Nodes.pm b/src/PMG/API2/Nodes.pm
index 96aa146..259f8f3 100644
--- a/src/PMG/API2/Nodes.pm
+++ b/src/PMG/API2/Nodes.pm
@@ -26,6 +26,7 @@ use PMG::API2::SpamAssassin;
use PMG::API2::Postfix;
use PMG::API2::MailTracker;
use PMG::API2::Backup;
+use PMG::API2::PBS::Job;
use base qw(PVE::RESTHandler);
@@ -79,6 +80,11 @@ __PACKAGE__->register_method ({
path => 'backup',
});
+__PACKAGE__->register_method ({
+ subclass => "PMG::API2::PBS::Job",
+ path => 'pbs',
+});
+
__PACKAGE__->register_method ({
name => 'index',
path => '',
@@ -105,6 +111,7 @@ __PACKAGE__->register_method ({
my $result = [
{ name => 'apt' },
{ name => 'backup' },
+ { name => 'pbs' },
{ name => 'clamav' },
{ name => 'spamassassin' },
{ name => 'postfix' },
diff --git a/src/PMG/API2/PBS/Job.pm b/src/PMG/API2/PBS/Job.pm
new file mode 100644
index 0000000..80a1f29
--- /dev/null
+++ b/src/PMG/API2/PBS/Job.pm
@@ -0,0 +1,413 @@
+package PMG::API2::PBS::Job;
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::PBSTools;
+
+use PMG::RESTEnvironment;
+use PMG::Backup;
+use PMG::PBSConfig;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ name => 'list',
+ path => '',
+ method => 'GET',
+ description => "List all configured Proxmox Backup Server jobs.",
+ permissions => { check => [ 'admin', 'audit' ] },
+ proxyto => 'node',
+ protected => 1,
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => "array",
+ items => PMG::PBSConfig->createSchema(1),
+ links => [ { rel => 'child', href => "{remote}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $res = [];
+
+ my $conf = PMG::PBSConfig->new();
+ if (defined($conf)) {
+ foreach my $remote (keys %{$conf->{ids}}) {
+ my $d = $conf->{ids}->{$remote};
+ my $entry = {
+ remote => $remote,
+ server => $d->{server},
+ datastore => $d->{datastore},
+ };
+ push @$res, $entry;
+ }
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'remote_index',
+ path => '{remote}',
+ method => 'GET',
+ description => "Backup Job index.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => { section => { type => 'string'} },
+ },
+ links => [ { rel => 'child', href => "{section}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $result = [
+ { section => 'snapshots' },
+ { section => 'backup' },
+ { section => 'restore' },
+ { section => 'timer' },
+ { section => 'prune' },
+ ];
+ return $result;
+}});
+
+__PACKAGE__->register_method ({
+ name => 'get_snapshots',
+ path => '{remote}/snapshots',
+ method => 'GET',
+ description => "Get snapshots stored on remote.",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ time => { type => 'string'},
+ ctime => { type => 'string'},
+ size => { type => 'integer'},
+ encrypted => { type => 'boolean'},
+ },
+ },
+ links => [ { rel => 'child', href => "{time}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+ my $node = $param->{node};
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+
+ return [] if $remote_config->{disable};
+
+ my $snap_param = {
+ group => "host/$node",
+ };
+ my $snapshots = PVE::PBSTools::get_snapshots($remote_config, $remote, $snap_param);
+ my $res = [];
+ foreach my $item (@$snapshots) {
+ my $btype = $item->{"backup-type"};
+ my $bid = $item->{"backup-id"};
+ my $epoch = $item->{"backup-time"};
+ my $size = $item->{size} // 1;
+
+ my @pxar = grep { $_->{filename} eq 'pmgbackup.pxar.didx' } @{$item->{files}};
+ die "unexpected number of pmgbackup archives in snapshot\n" if (scalar(@pxar) != 1);
+
+ my $encrypted = ($pxar[0]->{'crypt-mode'} eq 'encrypt') ? 1 : 0;
+
+
+ next if !($btype eq 'host');
+ next if !($bid eq $node);
+
+ my $time = strftime("%FT%TZ", gmtime($epoch));
+
+ my $info = {
+ time => $time,
+ ctime => $epoch,
+ size => $size,
+ encrypted => $encrypted,
+ };
+
+ push @$res, $info;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'forget_snapshot',
+ path => '{remote}/snapshots/{time}',
+ method => 'DELETE',
+ description => "Forget a snapshot",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ time => {
+ description => "Backup time in RFC 3399 format",
+ type => 'string',
+ },
+ },
+ },
+ returns => {type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+ my $node = $param->{node};
+ my $time = $param->{time};
+
+ my $snapshot = "host/$node/$time";
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+
+ eval {
+ PVE::PBSTools::forget_snapshot($remote_config, $remote, $snapshot);
+ };
+ die "Forgetting backup failed: $@" if $@;
+
+ return;
+
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'run_backup',
+ path => '{remote}/backup',
+ method => 'POST',
+ description => "run backup",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => { type => "string" },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PMG::RESTEnvironment->get();
+ my $authuser = $rpcenv->get_user();
+
+ my $remote = $param->{remote};
+ my $node = $param->{node};
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+
+ my $backup_dir = "/var/lib/pmg/backup/current";
+
+ my $worker = sub {
+ my $upid = shift;
+
+ print "starting update of current backup state\n";
+
+ PMG::Backup::pmg_backup($backup_dir, $param->{statistic});
+ my $pbs_opts = {
+ type => 'host',
+ id => $node,
+ pxarname => 'pmgbackup',
+ root => $backup_dir,
+ };
+
+ PVE::PBSTools::backup_tree($remote_config, $remote, $pbs_opts);
+
+ print "backup finished\n";
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('pbs_backup', undef, $authuser, $worker);
+
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'restore',
+ path => '{remote}/restore',
+ method => 'POST',
+ description => "Restore the system configuration.",
+ permissions => { check => [ 'admin' ] },
+ proxyto => 'node',
+ protected => 1,
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ PMG::Backup::get_restore_options(),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ 'backup-time' => {description=> "backup-time to restore",
+ optional => 1, type => 'string'
+ },
+ 'backup-id' => {description => "backup-id (hostname) of backup snapshot",
+ optional => 1, type => 'string'
+ },
+ },
+ },
+ returns => { type => "string" },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PMG::RESTEnvironment->get();
+ my $authuser = $rpcenv->get_user();
+
+ my $remote = $param->{remote};
+ my $backup_id = $param->{'backup-id'} // $param->{node};
+ my $snapshot = "host/$backup_id";
+ $snapshot .= "/$param->{'backup-time'}" if defined($param->{'backup-time'});
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+
+ my $time = time;
+ my $dirname = "/tmp/proxrestore_$$.$time";
+
+ $param->{database} //= 1;
+
+ die "nothing selected - please select what you want to restore (config or database?)\n"
+ if !($param->{database} || $param->{config});
+
+ my $pbs_opts = {
+ pxarname => 'pmgbackup',
+ target => $dirname,
+ snapshot => $snapshot,
+ };
+
+ my $worker = sub {
+ my $upid = shift;
+
+ print "starting restore of $snapshot from $remote\n";
+
+ PVE::PBSTools::restore_pxar($remote_config, $remote, $pbs_opts);
+ print "starting restore of PMG config\n";
+ PMG::Backup::pmg_restore($dirname, $param->{database},
+ $param->{config}, $param->{statistic});
+ print "restore finished\n";
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('pbs_restore', undef, $authuser, $worker);
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'run_prune',
+ path => '{remote}/prune',
+ method => 'POST',
+ description => "Prune the remote based on the configured options",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => { type => "string" },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PMG::RESTEnvironment->get();
+ my $authuser = $rpcenv->get_user();
+
+ my $remote = $param->{remote};
+ my $node = $param->{node};
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+
+ my $group = "host/$node";
+
+ my $worker = sub {
+ my $upid = shift;
+
+ print "starting prune of current backup state\n";
+
+ my $prune_opts = $conf->prune_options($remote);
+ my $res = PVE::PBSTools::prune_group($remote_config, $remote, $prune_opts, $group);
+
+ foreach my $pruned (@$res){
+ my $time = strftime("%FT%TZ", gmtime($pruned->{'backup-time'}));
+ my $snap = $pruned->{'backup-type'} . '/' . $pruned->{'backup-id'} . '/' . $time;
+ print "pruned snapshot: $snap\n";
+ }
+
+ print "prune finished\n";
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('pbs_backup', undef, $authuser, $worker);
+
+ }});
+1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 09/11] pbs-integration: add CLI calls to pmgbackup
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (9 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 08/11] Add API2 module for per-node backups to PBS Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 10/11] add scheduled backup to PBS remotes Stoiko Ivanov
` (5 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
add two new categories for commands to pmgbackup:
* pmgbackup remote - for managing PBS instances' configuration, cluster-wide
* pmgbackup pbsjob - for managing backups, restores, pruning
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/CLI/pmgbackup.pm | 96 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/src/PMG/CLI/pmgbackup.pm b/src/PMG/CLI/pmgbackup.pm
index 69224e5..2a85b76 100644
--- a/src/PMG/CLI/pmgbackup.pm
+++ b/src/PMG/CLI/pmgbackup.pm
@@ -3,14 +3,20 @@ package PMG::CLI::pmgbackup;
use strict;
use warnings;
use Data::Dumper;
+use POSIX qw(strftime);
use PVE::Tools;
use PVE::SafeSyslog;
use PVE::INotify;
use PVE::CLIHandler;
+use PVE::CLIFormatter;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::PBSTools;
use PMG::RESTEnvironment;
use PMG::API2::Backup;
+use PMG::API2::PBS::Remote;
+use PMG::API2::PBS::Job;
use base qw(PVE::CLIHandler);
@@ -28,10 +34,100 @@ my $format_backup_list = sub {
}
};
+__PACKAGE__->register_method ({
+ name => 'prune',
+ path => 'prune',
+ method => 'GET',
+ description => "Prune all remotes",
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "only prune the provided Proxmox Backup Server.",
+ type => 'string', format => 'pve-configid',
+ optional => 1,
+ },
+ 'dry-run' => { type => 'boolean', optional => 1, default => 0},
+ 'verbose' => { type => 'boolean', optional => 1, default => 0},
+ },
+ },
+ returns => { type => "string" },
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+ my $node = $param->{node};
+
+ my $verbose = defined($param->{verbose}) && $param->{verbose};
+
+ my $group = "host/$node";
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $pbs_opts = {};
+ if (defined($param->{'dry-run'}) && $param->{'dry-run'}) {
+ $pbs_opts->{'dry-run'} = 1;
+ }
+
+ my $run_prune = sub {
+ my ($remote_config, $remote) = @_;
+
+ print "running prune for $remote\n" if $verbose;
+
+ my $prune_opts = $conf->prune_options($remote);
+ my $res = PVE::PBSTools::prune_group($remote_config, $remote, $pbs_opts, $prune_opts, $group);
+
+ if ($verbose) {
+ foreach my $pruned (@$res){
+ my $time = strftime("%FT%TZ", gmtime($pruned->{'backup-time'}));
+ my $snap = $pruned->{'backup-type'} . '/' . $pruned->{'backup-id'} . '/' . $time;
+ print "pruned snapshot: $snap\n";
+ }
+
+ }
+ print "finished pruning $remote\n" if $verbose;
+ };
+
+ if (defined($remote)) {
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+ $run_prune->($remote_config, $remote);
+ } else {
+ foreach my $remote (sort keys %{$conf->{ids}}){
+ my $cfg = $conf->{ids}->{$remote};
+ next if $cfg->{disable};
+ $run_prune->($cfg, $remote);
+ }
+ }
+ }});
+
our $cmddef = {
backup => [ 'PMG::API2::Backup', 'backup', undef, { node => $nodename } ],
restore => [ 'PMG::API2::Backup', 'restore', undef, { node => $nodename } ],
list => [ 'PMG::API2::Backup', 'list', undef, { node => $nodename }, $format_backup_list ],
+ remote => {
+ list => ['PMG::API2::PBS::Remote', 'list', undef, undef, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, ['remote', 'server', 'datastore', 'username' ], $options);
+ }, $PVE::RESTHandler::standard_output_options ],
+ add => ['PMG::API2::PBS::Remote', 'create', ['remote'] ],
+ remove => ['PMG::API2::PBS::Remote', 'delete', ['remote'] ],
+ set => ['PMG::API2::PBS::Remote', 'update_config', ['remote'] ],
+ },
+ pbsjob => {
+ list_backups => ['PMG::API2::PBS::Job', 'get_snapshots', ['remote'] , { node => $nodename }, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, ['time', 'size'], $options);
+ }, $PVE::RESTHandler::standard_output_options ],
+ forget => ['PMG::API2::PBS::Job', 'forget_snapshot', ['remote', 'time'], { node => $nodename} ],
+ run => ['PMG::API2::PBS::Job', 'run_backup', ['remote'], { node => $nodename} ],
+ restore => ['PMG::API2::PBS::Job', 'restore', ['remote'], { node => $nodename} ],
+ prune => [__PACKAGE__, 'prune', undef , { node => $nodename } ],
+ },
};
1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 10/11] add scheduled backup to PBS remotes
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (10 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 09/11] pbs-integration: add CLI calls to pmgbackup Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 11/11] add daily timer for pruning configured remotes Stoiko Ivanov
` (4 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
PMG::PBSSchedule contains methods for creating/deleting systemd-timer units,
which will run a backup to a configured PBS remote.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
debian/pmg-pbsbackup@.service | 6 ++
debian/rules | 1 +
src/Makefile | 3 +-
src/PMG/API2/PBS/Job.pm | 131 ++++++++++++++++++++++++++++++++++
src/PMG/CLI/pmgbackup.pm | 6 ++
src/PMG/PBSSchedule.pm | 104 +++++++++++++++++++++++++++
6 files changed, 250 insertions(+), 1 deletion(-)
create mode 100644 debian/pmg-pbsbackup@.service
create mode 100644 src/PMG/PBSSchedule.pm
diff --git a/debian/pmg-pbsbackup@.service b/debian/pmg-pbsbackup@.service
new file mode 100644
index 0000000..37aa23b
--- /dev/null
+++ b/debian/pmg-pbsbackup@.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Backup to PBS remote %I
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/pmgbackup pbsjob run %I
diff --git a/debian/rules b/debian/rules
index bab4d98..5a2cf7a 100755
--- a/debian/rules
+++ b/debian/rules
@@ -20,6 +20,7 @@ override_dh_installinit:
dh_systemd_enable --name=pmgspamreport pmgspamreport.service
dh_systemd_enable --name=pmgreport pmgreport.service
dh_systemd_enable --name=pmgsync pmgsync.service
+ dh_systemd_enable --no-enable --name=pmg-pbsbackup@ pmg-pbsbackup@.service
override_dh_systemd_start:
dh_systemd_start pmg-hourly.timer pmg-daily.timer pmgspamreport.timer pmgreport.timer
diff --git a/src/Makefile b/src/Makefile
index fb42f21..9d5c335 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -15,7 +15,7 @@ CRONSCRIPTS = pmg-hourly pmg-daily
CLI_CLASSES = $(addprefix PMG/CLI/, $(addsuffix .pm, ${CLITOOLS}))
SERVICE_CLASSES = $(addprefix PMG/Service/, $(addsuffix .pm, ${SERVICES}))
-SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES}))
+SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES} pmg-pbsbackup@))
TIMER_UNITS = $(addprefix debian/, $(addsuffix .timer, ${CRONSCRIPTS} pmgspamreport pmgreport))
CLI_BINARIES = $(addprefix bin/, ${CLITOOLS} ${CLISCRIPTS} ${CRONSCRIPTS})
@@ -67,6 +67,7 @@ LIBSOURCES = \
PMG/Unpack.pm \
PMG/Backup.pm \
PMG/PBSConfig.pm \
+ PMG/PBSSchedule.pm \
PMG/RuleCache.pm \
PMG/Statistic.pm \
PMG/UserConfig.pm \
diff --git a/src/PMG/API2/PBS/Job.pm b/src/PMG/API2/PBS/Job.pm
index 80a1f29..088873e 100644
--- a/src/PMG/API2/PBS/Job.pm
+++ b/src/PMG/API2/PBS/Job.pm
@@ -14,6 +14,7 @@ use PVE::PBSTools;
use PMG::RESTEnvironment;
use PMG::Backup;
use PMG::PBSConfig;
+use PMG::PBSSchedule;
use base qw(PVE::RESTHandler);
@@ -410,4 +411,134 @@ __PACKAGE__->register_method ({
return $rpcenv->fork_worker('pbs_backup', undef, $authuser, $worker);
}});
+
+__PACKAGE__->register_method ({
+ name => 'create_timer',
+ path => '{remote}/timer',
+ method => 'POST',
+ description => "Create backup schedule",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ schedule => {
+ description => "Schedule for the backup (OnCalendar setting of the systemd.timer)",
+ type => 'string', pattern => '[0-9a-zA-Z*.:,\-/ ]+',
+ default => 'daily', optional => 1,
+ },
+ delay => {
+ description => "Randomized delay to add to the starttime (RandomizedDelaySec setting of the systemd.timer)",
+ type => 'string', pattern => '[0-9a-zA-Z. ]+',
+ default => 'daily', optional => 1,
+ },
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+ my $schedule = $param->{schedule} // 'daily';
+ my $delay = $param->{delay} // '5min';
+
+ my $conf = PMG::PBSConfig->new();
+
+ my $remote_config = $conf->{ids}->{$remote};
+ die "PBS remote '$remote' does not exist\n" if !$remote_config;
+ die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
+
+ PMG::PBSSchedule::create_schedule($remote, $schedule, $delay);
+
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete_timer',
+ path => '{remote}/timer',
+ method => 'DELETE',
+ description => "Delete backup schedule",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+
+ PMG::PBSSchedule::delete_schedule($remote);
+
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'list_timer',
+ path => '{remote}/timer',
+ method => 'GET',
+ description => "Get timer specification",
+ proxyto => 'node',
+ protected => 1,
+ permissions => { check => [ 'admin', 'audit' ] },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ },
+ },
+ },
+ returns => { type => 'object', properties => {
+ remote => {
+ description => "Proxmox Backup Server ID.",
+ type => 'string', format => 'pve-configid',
+ optional => 1,
+ },
+ schedule => {
+ description => "Schedule for the backup (OnCalendar setting of the systemd.timer)",
+ type => 'string', pattern => '[0-9a-zA-Z*.:,\-/ ]+',
+ default => 'daily', optional => 1,
+ },
+ delay => {
+ description => "Randomized delay to add to the starttime (RandomizedDelaySec setting of the systemd.timer)",
+ type => 'string', pattern => '[0-9a-zA-Z. ]+',
+ default => 'daily', optional => 1,
+ },
+ unitfile => {
+ description => "unit file for the systemd.timer unit",
+ type => 'string', optional => 1,
+ },
+ }},
+ code => sub {
+ my ($param) = @_;
+
+ my $remote = $param->{remote};
+
+ my $schedules = PMG::PBSSchedule::get_schedules();
+ my @data = grep {$_->{remote} eq $remote} @$schedules;
+
+ my $res = {};
+ if (scalar(@data) == 1) {
+ $res = $data[0];
+ }
+
+ return $res
+ }});
+
1;
diff --git a/src/PMG/CLI/pmgbackup.pm b/src/PMG/CLI/pmgbackup.pm
index 2a85b76..0f1c6a9 100644
--- a/src/PMG/CLI/pmgbackup.pm
+++ b/src/PMG/CLI/pmgbackup.pm
@@ -127,6 +127,12 @@ our $cmddef = {
run => ['PMG::API2::PBS::Job', 'run_backup', ['remote'], { node => $nodename} ],
restore => ['PMG::API2::PBS::Job', 'restore', ['remote'], { node => $nodename} ],
prune => [__PACKAGE__, 'prune', undef , { node => $nodename } ],
+ create => ['PMG::API2::PBS::Job', 'create_timer', ['remote'], { node => $nodename }],
+ delete => ['PMG::API2::PBS::Job', 'delete_timer', ['remote'], { node => $nodename }],
+ schedule => ['PMG::API2::PBS::Job', 'list_timer', ['remote'], { node => $nodename }, sub {
+ my ($data, $schema, $options) = @_;
+ PVE::CLIFormatter::print_api_result($data, $schema, ['remote', 'schedule', 'delay'], $options);
+ }, $PVE::RESTHandler::standard_output_options ],
},
};
diff --git a/src/PMG/PBSSchedule.pm b/src/PMG/PBSSchedule.pm
new file mode 100644
index 0000000..6663f55
--- /dev/null
+++ b/src/PMG/PBSSchedule.pm
@@ -0,0 +1,104 @@
+package PMG::PBSSchedule;
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(run_command file_set_contents file_get_contents trim dir_glob_foreach);
+use PVE::Systemd;
+
+# systemd timer
+sub get_schedules {
+ my ($param) = @_;
+
+ my $result = [];
+
+ my $systemd_dir = '/etc/systemd/system';
+
+ dir_glob_foreach($systemd_dir, '^pmg-pbsbackup@.+\.timer$', sub {
+ my ($filename) = @_;
+ my $remote;
+ if ($filename =~ /^pmg-pbsbackup\@(.+)\.timer$/) {
+ $remote = PVE::Systemd::unescape_unit($1);
+ } else {
+ die 'Unrecognized timer name!\n';
+ }
+
+ my $unitfile = "$systemd_dir/$filename";
+ my $unit = PVE::Systemd::read_ini($unitfile);
+
+ push @$result, {
+ unitfile => $unitfile,
+ remote => $remote,
+ schedule => $unit->{'Timer'}->{'OnCalendar'},
+ delay => $unit->{'Timer'}->{'RandomizedDelaySec'},
+ };
+ });
+
+ return $result;
+
+}
+
+sub create_schedule {
+ my ($remote, $schedule, $delay) = @_;
+
+ my $unit_name = 'pmg-pbsbackup@' . PVE::Systemd::escape_unit($remote);
+ #my $service_unit = $unit_name . '.service';
+ my $timer_unit = $unit_name . '.timer';
+ my $timer_unit_path = "/etc/systemd/system/$timer_unit";
+
+ # create systemd timer
+ run_command(['systemd-analyze', 'calendar', $schedule], errmsg => "Invalid schedule specification", outfunc => sub {});
+ run_command(['systemd-analyze', 'timespan', $delay], errmsg => "Invalid delay specification", outfunc => sub {});
+ my $timer = {
+ 'Unit' => {
+ 'Description' => "Timer for PBS Backup to remote $remote",
+ },
+ 'Timer' => {
+ 'OnCalendar' => $schedule,
+ 'RandomizedDelaySec' => $delay,
+ },
+ 'Install' => {
+ 'WantedBy' => 'timers.target',
+ },
+ };
+
+ eval {
+ PVE::Systemd::write_ini($timer, $timer_unit_path);
+ run_command(['systemctl', 'daemon-reload']);
+ run_command(['systemctl', 'enable', $timer_unit]);
+ run_command(['systemctl', 'start', $timer_unit]);
+
+ };
+ if (my $err = $@) {
+ die "Creating backup schedule for $remote failed: $err\n";
+ }
+
+ return;
+}
+
+sub delete_schedule {
+ my ($remote) = @_;
+
+ my $schedules = get_schedules();
+
+ die "Schedule for $remote not found!\n" if !grep {$_->{remote} eq $remote} @$schedules;
+
+ my $unit_name = 'pmg-pbsbackup@' . PVE::Systemd::escape_unit($remote);
+ my $service_unit = $unit_name . '.service';
+ my $timer_unit = $unit_name . '.timer';
+ my $timer_unit_path = "/etc/systemd/system/$timer_unit";
+
+ eval {
+ run_command(['systemctl', 'disable', $timer_unit]);
+ unlink($timer_unit_path) || die "delete '$timer_unit_path' failed - $!\n";
+ run_command(['systemctl', 'daemon-reload']);
+
+ };
+ if (my $err = $@) {
+ die "Removing backup schedule for $remote failed: $err\n";
+ }
+
+ return;
+}
+
+1;
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-api 11/11] add daily timer for pruning configured remotes
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (11 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 10/11] add scheduled backup to PBS remotes Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 1/3] Make Backup/Restore panel a menuentry Stoiko Ivanov
` (3 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
The service runs `pmgbackup pbsjob prune` , which iterates through all
configured remotes and runs prune with the set keep settings.
Running once daily seems like a sensible tradeoff (prune is not so expensive)
adding the dedicated systemd.timer, instead of simply hooking it into
pmg-daily, should make it easier for users who want to override the settings.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
debian/pmg-pbsprune.service | 6 ++++++
debian/pmg-pbsprune.timer | 10 ++++++++++
debian/rules | 3 ++-
src/Makefile | 4 ++--
4 files changed, 20 insertions(+), 3 deletions(-)
create mode 100644 debian/pmg-pbsprune.service
create mode 100644 debian/pmg-pbsprune.timer
diff --git a/debian/pmg-pbsprune.service b/debian/pmg-pbsprune.service
new file mode 100644
index 0000000..b7fd633
--- /dev/null
+++ b/debian/pmg-pbsprune.service
@@ -0,0 +1,6 @@
+[Unit]
+Description=Prune all configured Proxmox Backup Server Remotes
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/pmgbackup pbsjob prune
diff --git a/debian/pmg-pbsprune.timer b/debian/pmg-pbsprune.timer
new file mode 100644
index 0000000..52c7e06
--- /dev/null
+++ b/debian/pmg-pbsprune.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Prune all configured Proxmox Backup Server Remotes
+
+[Timer]
+OnCalendar=04:00
+RandomizedDelaySec=1hours
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff --git a/debian/rules b/debian/rules
index 5a2cf7a..227b761 100755
--- a/debian/rules
+++ b/debian/rules
@@ -21,9 +21,10 @@ override_dh_installinit:
dh_systemd_enable --name=pmgreport pmgreport.service
dh_systemd_enable --name=pmgsync pmgsync.service
dh_systemd_enable --no-enable --name=pmg-pbsbackup@ pmg-pbsbackup@.service
+ dh_systemd_enable --name=pmg-pbsprune pmg-pbsprune.service
override_dh_systemd_start:
- dh_systemd_start pmg-hourly.timer pmg-daily.timer pmgspamreport.timer pmgreport.timer
+ dh_systemd_start pmg-hourly.timer pmg-daily.timer pmgspamreport.timer pmgreport.timer pmg-pbsprune.timer
dh_systemd_start --no-restart-on-upgrade --no-start pmgnetcommit.service pmgbanner.service pmgsync.service
dh_systemd_start pmg-smtp-filter.service pmgpolicy.service pmgtunnel.service pmgmirror.service
# we handle pmgproxy/pmgdaemon manually (use reload instead of restart to keep vnc connection active)
diff --git a/src/Makefile b/src/Makefile
index 9d5c335..85e91ce 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -15,8 +15,8 @@ CRONSCRIPTS = pmg-hourly pmg-daily
CLI_CLASSES = $(addprefix PMG/CLI/, $(addsuffix .pm, ${CLITOOLS}))
SERVICE_CLASSES = $(addprefix PMG/Service/, $(addsuffix .pm, ${SERVICES}))
-SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES} pmg-pbsbackup@))
-TIMER_UNITS = $(addprefix debian/, $(addsuffix .timer, ${CRONSCRIPTS} pmgspamreport pmgreport))
+SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES} pmg-pbsbackup@ pmg-pbsprune))
+TIMER_UNITS = $(addprefix debian/, $(addsuffix .timer, ${CRONSCRIPTS} pmgspamreport pmgreport pmg-pbsprune))
CLI_BINARIES = $(addprefix bin/, ${CLITOOLS} ${CLISCRIPTS} ${CRONSCRIPTS})
CLI_MANS = $(addsuffix .1, ${CLITOOLS}) pmgsh.1 pmg-system-report.1
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-gui 1/3] Make Backup/Restore panel a menuentry
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (12 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 11/11] add daily timer for pruning configured remotes Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 2/3] refactor RestoreWindow for PBS Stoiko Ivanov
` (2 subsequent siblings)
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
Move it away from the tab list in the Configuration entry to a submenu in
preparation for adding PBS integration
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/BackupConfiguration.js | 18 ++++++++++++++++++
js/Makefile | 1 +
js/NavigationTree.js | 6 ++++++
js/SystemConfiguration.js | 4 ----
4 files changed, 25 insertions(+), 4 deletions(-)
create mode 100644 js/BackupConfiguration.js
diff --git a/js/BackupConfiguration.js b/js/BackupConfiguration.js
new file mode 100644
index 0000000..35b50a4
--- /dev/null
+++ b/js/BackupConfiguration.js
@@ -0,0 +1,18 @@
+Ext.define('PMG.BackupConfiguration', {
+ extend: 'Ext.tab.Panel',
+ alias: 'widget.pmgBackupConfiguration',
+
+ title: gettext('Backup'),
+
+ border: false,
+ defaults: { border: false },
+
+ items: [
+ {
+ itemId: 'local',
+ title: gettext('Local Backup/Restore'),
+ xtype: 'pmgBackupRestore',
+ },
+ ],
+});
+
diff --git a/js/Makefile b/js/Makefile
index badf7ab..a40f11f 100644
--- a/js/Makefile
+++ b/js/Makefile
@@ -35,6 +35,7 @@ JSSRC= \
RuleConfiguration.js \
SystemOptions.js \
Subscription.js \
+ BackupConfiguration.js \
BackupRestore.js \
SystemConfiguration.js \
MailProxyRelaying.js \
diff --git a/js/NavigationTree.js b/js/NavigationTree.js
index 0ea0d2f..ac01fd6 100644
--- a/js/NavigationTree.js
+++ b/js/NavigationTree.js
@@ -86,6 +86,12 @@ Ext.define('PMG.store.NavigationStore', {
path: 'pmgSubscription',
leaf: true,
},
+ {
+ text: gettext('Backup/Restore'),
+ iconCls: 'fa fa-floppy-o',
+ path: 'pmgBackupConfiguration',
+ leaf: true,
+ },
],
},
{
diff --git a/js/SystemConfiguration.js b/js/SystemConfiguration.js
index 37cb3e4..51b558a 100644
--- a/js/SystemConfiguration.js
+++ b/js/SystemConfiguration.js
@@ -49,10 +49,6 @@ Ext.define('PMG.SystemConfiguration', {
title: gettext('Options'),
xtype: 'pmgSystemOptions',
},
- {
- itemId: 'backup',
- xtype: 'pmgBackupRestore',
- },
],
initComponent: function() {
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-gui 2/3] refactor RestoreWindow for PBS
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (13 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 1/3] Make Backup/Restore panel a menuentry Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 3/3] add PBSConfig tab to Backup menu Stoiko Ivanov
2020-10-30 5:47 ` [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Dietmar Maurer
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
by moving the item definition to initComponent, and changing the check
for a provided filename, we can reuse the window for restores from PBS
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/BackupRestore.js | 59 +++++++++++++++++++++++----------------------
1 file changed, 30 insertions(+), 29 deletions(-)
diff --git a/js/BackupRestore.js b/js/BackupRestore.js
index 6c97230..996f128 100644
--- a/js/BackupRestore.js
+++ b/js/BackupRestore.js
@@ -26,40 +26,41 @@ Ext.define('PMG.RestoreWindow', {
fieldDefaults: {
labelWidth: 150,
},
- items: [
- {
- xtype: 'proxmoxcheckbox',
- name: 'config',
- fieldLabel: gettext('System Configuration'),
- },
- {
- xtype: 'proxmoxcheckbox',
- name: 'database',
- value: 1,
- uncheckedValue: 0,
- fieldLabel: gettext('Rule Database'),
- listeners: {
- change: function(cb, value) {
- var me = this;
- me.up().down('field[name=statistic]').setDisabled(!value);
- },
- },
- },
- {
- xtype: 'proxmoxcheckbox',
- name: 'statistic',
- fieldLabel: gettext('Statistic'),
- },
- ],
initComponent: function() {
var me = this;
- if (!me.filename) {
- throw "no filename given";
- }
+ me.items = [
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'config',
+ fieldLabel: gettext('System Configuration'),
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'database',
+ value: 1,
+ uncheckedValue: 0,
+ fieldLabel: gettext('Rule Database'),
+ listeners: {
+ change: function(cb, value) {
+ me.up().down('field[name=statistic]').setDisabled(!value);
+ },
+ },
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'statistic',
+ fieldLabel: gettext('Statistic'),
+ },
+ ];
- me.url = "/nodes/" + Proxmox.NodeName + "/backup/" + encodeURIComponent(me.filename);
+ let initurl = "/nodes/" + Proxmox.NodeName;
+ if (me.filename) {
+ me.url = initurl + "/backup/" + encodeURIComponent(me.filename);
+ } else {
+ throw "neither filename nor snapshot given";
+ }
me.callParent();
},
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] [PATCH pmg-gui 3/3] add PBSConfig tab to Backup menu
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (14 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 2/3] refactor RestoreWindow for PBS Stoiko Ivanov
@ 2020-10-28 18:54 ` Stoiko Ivanov
2020-10-30 5:47 ` [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Dietmar Maurer
16 siblings, 0 replies; 26+ messages in thread
From: Stoiko Ivanov @ 2020-10-28 18:54 UTC (permalink / raw)
To: pmg-devel
The PBSConfig panel enables creation/editing/deletion of PBS instances.
Each instance can lists its snapshots and each snapshot can be restored
Inspired by the LDAPConfig panel and PBSEdit from pve-manager.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/BackupConfiguration.js | 5 +
js/BackupRestore.js | 9 +
js/Makefile | 1 +
js/PBSConfig.js | 680 ++++++++++++++++++++++++++++++++++++++
4 files changed, 695 insertions(+)
create mode 100644 js/PBSConfig.js
diff --git a/js/BackupConfiguration.js b/js/BackupConfiguration.js
index 35b50a4..e21771f 100644
--- a/js/BackupConfiguration.js
+++ b/js/BackupConfiguration.js
@@ -13,6 +13,11 @@ Ext.define('PMG.BackupConfiguration', {
title: gettext('Local Backup/Restore'),
xtype: 'pmgBackupRestore',
},
+ {
+ itemId: 'proxmoxbackupserver',
+ title: 'Proxmox Backup Server',
+ xtype: 'pmgPBSConfig',
+ },
],
});
diff --git a/js/BackupRestore.js b/js/BackupRestore.js
index 996f128..9981d42 100644
--- a/js/BackupRestore.js
+++ b/js/BackupRestore.js
@@ -58,6 +58,15 @@ Ext.define('PMG.RestoreWindow', {
let initurl = "/nodes/" + Proxmox.NodeName;
if (me.filename) {
me.url = initurl + "/backup/" + encodeURIComponent(me.filename);
+ } else if (me.backup_time) {
+ me.items.push(
+ {
+ xtype: 'hiddenfield',
+ name: 'backup-time',
+ value: me.backup_time,
+ },
+ );
+ me.url = initurl + "/pbs/" + me.name + '/restore';
} else {
throw "neither filename nor snapshot given";
}
diff --git a/js/Makefile b/js/Makefile
index a40f11f..bc14487 100644
--- a/js/Makefile
+++ b/js/Makefile
@@ -37,6 +37,7 @@ JSSRC= \
Subscription.js \
BackupConfiguration.js \
BackupRestore.js \
+ PBSConfig.js \
SystemConfiguration.js \
MailProxyRelaying.js \
MailProxyPorts.js \
diff --git a/js/PBSConfig.js b/js/PBSConfig.js
new file mode 100644
index 0000000..abec9d7
--- /dev/null
+++ b/js/PBSConfig.js
@@ -0,0 +1,680 @@
+Ext.define('Proxmox.form.PBSEncryptionCheckbox', {
+ extend: 'Ext.form.field.Checkbox',
+ xtype: 'pbsEncryptionCheckbox',
+
+ inputValue: true,
+
+ viewModel: {
+ data: {
+ value: null,
+ originalValue: null,
+ },
+ formulas: {
+ blabel: (get) => {
+ let v = get('value');
+ let original = get('originalValue');
+ if (!get('isCreate') && original) {
+ if (!v) {
+ return gettext('Warning: Existing encryption key will be deleted!');
+ }
+ return gettext('Active');
+ } else {
+ return gettext('Auto-generate a client encryption key, saved privately in /etc/pmg');
+ }
+ },
+ },
+ },
+
+ bind: {
+ value: '{value}',
+ boxLabel: '{blabel}',
+ },
+ resetOriginalValue: function() {
+ let me = this;
+ let vm = me.getViewModel();
+ vm.set('originalValue', me.value);
+
+ me.callParent(arguments);
+ },
+
+ getSubmitData: function() {
+ let me = this;
+ let val = me.getSubmitValue();
+ if (!me.isCreate) {
+ if (val === null) {
+ return { 'delete': 'encryption-key' };
+ } else if (val && !!val !== !!me.originalValue) {
+ return { 'encryption-key': 'autogen' };
+ }
+ } else if (val) {
+ return { 'encryption-key': 'autogen' };
+ }
+ return null;
+ },
+
+ initComponent: function() {
+ let me = this;
+ me.callParent();
+
+ let vm = me.getViewModel();
+ vm.set('isCreate', me.isCreate);
+ },
+});
+
+Ext.define('PMG.PBSInputPanel', {
+ extend: 'Ext.tab.Panel',
+ xtype: 'pmgPBSInputPanel',
+
+ bodyPadding: 10,
+ remoteId: undefined,
+
+ initComponent: function() {
+ let me = this;
+
+ me.items = [
+ {
+ title: gettext('Backup Server'),
+ xtype: 'inputpanel',
+ reference: 'remoteeditpanel',
+ onGetValues: function(values) {
+ let me = this;
+
+ values.disable = values.enable ? 0 : 1;
+ delete values.enable;
+
+ return values;
+ },
+
+ column1: [
+ {
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
+ name: 'remote',
+ value: me.isCreate ? null : undefined,
+ fieldLabel: gettext('ID'),
+ allowBlank: false,
+ },
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'server',
+ value: me.isCreate ? null : undefined,
+ vtype: 'DnsOrIp',
+ fieldLabel: gettext('Server'),
+ allowBlank: false,
+ },
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'datastore',
+ value: me.isCreate ? null : undefined,
+ fieldLabel: 'Datastore',
+ allowBlank: false,
+ },
+ ],
+ column2: [
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'username',
+ value: me.isCreate ? null : undefined,
+ emptyText: gettext('Example') + ': admin@pbs',
+ fieldLabel: gettext('Username'),
+ regex: /\S+@\w+/,
+ regexText: gettext('Example') + ': admin@pbs',
+ allowBlank: false,
+ },
+ {
+ xtype: 'proxmoxtextfield',
+ inputType: 'password',
+ name: 'password',
+ value: me.isCreate ? null : undefined,
+ emptyText: me.isCreate ? gettext('None') : '********',
+ fieldLabel: gettext('Password'),
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'enable',
+ checked: true,
+ uncheckedValue: 0,
+ fieldLabel: gettext('Enable'),
+ },
+ ],
+ columnB: [
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'fingerprint',
+ value: me.isCreate ? null : undefined,
+ fieldLabel: gettext('Fingerprint'),
+ emptyText: gettext('Server certificate SHA-256 fingerprint, required for self-signed certificates'),
+ regex: /[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){31}/,
+ regexText: gettext('Example') + ': AB:CD:EF:...',
+ allowBlank: true,
+ },
+ {
+ xtype: 'pbsEncryptionCheckbox',
+ name: 'encryption-key',
+ isCreate: me.isCreate,
+ fieldLabel: gettext('Encryption Key'),
+ },
+ {
+ xtype: 'displayfield',
+ userCls: 'pmx-hint',
+ value: `Proxmox Backup Server is currently in beta.`,
+ },
+ ],
+ },
+ {
+ title: gettext('Prune Options'),
+ xtype: 'inputpanel',
+ reference: 'prunepanel',
+ column1: [
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Last'),
+ name: 'keep-last',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Daily'),
+ name: 'keep-daily',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Monthly'),
+ name: 'keep-monthly',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ ],
+ column2: [
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Hourly'),
+ name: 'keep-hourly',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Weekly'),
+ name: 'keep-weekly',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Keep Yearly'),
+ name: 'keep-yearly',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ minValue: 1,
+ allowBlank: true,
+ },
+ ],
+ },
+ ];
+
+ me.callParent();
+ },
+
+});
+
+Ext.define('PMG.PBSEdit', {
+ extend: 'Proxmox.window.Edit',
+ xtype: 'pmgPBSEdit',
+
+ subject: 'Proxmox Backup Server',
+ isAdd: true,
+
+ bodyPadding: 0,
+
+ initComponent: function() {
+ let me = this;
+
+ me.isCreate = !me.remoteId;
+
+ if (me.isCreate) {
+ me.url = '/api2/extjs/config/pbs';
+ me.method = 'POST';
+ } else {
+ me.url = '/api2/extjs/config/pbs/' + me.remoteId;
+ me.method = 'PUT';
+ }
+
+ let ipanel = Ext.create('PMG.PBSInputPanel', {
+ isCreate: me.isCreate,
+ remoteId: me.remoteId,
+ });
+
+ me.items = [ipanel];
+
+ me.fieldDefaults = {
+ labelWidth: 150,
+ };
+
+ me.callParent();
+
+ if (!me.isCreate) {
+ me.load({
+ success: function(response, options) {
+ let values = response.result.data;
+
+ values.enable = values.disable ? 0 : 1;
+ me.down('inputpanel[reference=remoteeditpanel]').setValues(values);
+ me.down('inputpanel[reference=prunepanel]').setValues(values);
+ },
+ });
+ }
+ },
+});
+
+Ext.define('PMG.PBSScheduleEdit', {
+ extend: 'Proxmox.window.Edit',
+ xtype: 'pmgPBSScheduleEdit',
+
+ isAdd: true,
+ method: 'POST',
+ subject: gettext('Scheduled Backup'),
+ autoLoad: true,
+ items: [
+ {
+ xtype: 'proxmoxKVComboBox',
+ name: 'schedule',
+ fieldLabel: gettext('Schedule'),
+ comboItems: [
+ ['daily', 'daily'],
+ ['hourly', 'hourly'],
+ ['weekly', 'weekly'],
+ ['monthly', 'monthly'],
+ ],
+ editable: true,
+ emptyText: 'Systemd Calender Event',
+ },
+ {
+ xtype: 'proxmoxKVComboBox',
+ name: 'delay',
+ fieldLabel: gettext('Random Delay'),
+ comboItems: [
+ ['0s', 'no delay'],
+ ['15 minutes', '15 Minutes'],
+ ['6 hours', '6 hours'],
+ ],
+ editable: true,
+ emptyText: 'Systemd TimeSpan',
+ },
+ ],
+ initComponent: function() {
+ let me = this;
+
+ me.url = '/nodes/' + Proxmox.NodeName + '/pbs/' + me.remote + '/timer';
+ me.callParent();
+ },
+});
+
+Ext.define('PMG.PBSConfig', {
+ extend: 'Ext.panel.Panel',
+ xtype: 'pmgPBSConfig',
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ callRestore: function(grid, record) {
+ let name = this.getViewModel().get('name');
+ Ext.create('PMG.RestoreWindow', {
+ name: name,
+ backup_time: record.data.time,
+ }).show();
+ },
+
+ restoreSnapshot: function(button) {
+ let me = this;
+ let view = me.lookup('pbsremotegrid');
+ let record = view.getSelection()[0];
+ me.callRestore(view, record);
+ },
+
+ runBackup: function(button) {
+ let me = this;
+ let view = me.lookup('pbsremotegrid');
+ let name = me.getViewModel().get('name');
+ Proxmox.Utils.API2Request({
+ url: "/nodes/" + Proxmox.NodeName + "/pbs/" + name + "/backup",
+ method: 'POST',
+ waitMsgTarget: view,
+ failure: function(response, opts) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ },
+ success: function(response, opts) {
+ let upid = response.result.data;
+
+ let win = Ext.create('Proxmox.window.TaskViewer', {
+ upid: upid,
+ });
+ win.show();
+ me.mon(win, 'close', function() { view.getStore().load(); });
+ },
+ });
+ },
+
+ reload: function(grid) {
+ let me = this;
+ let selection = grid.getSelection();
+ me.showInfo(grid, selection);
+ },
+
+ showInfo: function(grid, selected) {
+ let me = this;
+ let viewModel = me.getViewModel();
+ if (selected[0]) {
+ let name = selected[0].data.remote;
+ viewModel.set('selected', true);
+ viewModel.set('name', name);
+
+ // set grid stores and load them
+ let remstore = me.lookup('pbsremotegrid').getStore();
+ remstore.getProxy().setUrl('/api2/json/nodes/' + Proxmox.NodeName + '/pbs/' + name + '/snapshots');
+ remstore.load();
+ } else {
+ viewModel.set('selected', false);
+ }
+ },
+ reloadSnapshots: function() {
+ let me = this;
+ let grid = me.lookup('grid');
+ let selection = grid.getSelection();
+ me.showInfo(grid, selection);
+ },
+ init: function(view) {
+ let me = this;
+ me.lookup('grid').relayEvents(view, ['activate']);
+ let pbsremotegrid = me.lookup('pbsremotegrid');
+
+ Proxmox.Utils.monStoreErrors(pbsremotegrid, pbsremotegrid.getStore(), true);
+ },
+
+ control: {
+ 'grid[reference=grid]': {
+ selectionchange: 'showInfo',
+ load: 'reload',
+ },
+ 'grid[reference=pbsremotegrid]': {
+ itemdblclick: 'restoreSnapshot',
+ },
+ },
+ },
+
+ viewModel: {
+ data: {
+ name: '',
+ selected: false,
+ },
+ },
+
+ layout: 'border',
+
+ items: [
+ {
+ region: 'center',
+ reference: 'grid',
+ xtype: 'pmgPBSConfigGrid',
+ border: false,
+ },
+ {
+ xtype: 'grid',
+ region: 'south',
+ reference: 'pbsremotegrid',
+ hidden: true,
+ height: '70%',
+ border: false,
+ split: true,
+ emptyText: gettext('No backups on remote'),
+ tbar: [
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Backup'),
+ handler: 'runBackup',
+ selModel: false,
+ },
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Restore'),
+ handler: 'restoreSnapshot',
+ disabled: true,
+ },
+ {
+ xtype: 'proxmoxStdRemoveButton',
+ text: gettext('Forget Snapshot'),
+ disabled: true,
+ getUrl: function(rec) {
+ let me = this;
+ let remote = me.lookupViewModel().get('name');
+ return '/nodes/' + Proxmox.NodeName + '/pbs/' + remote +'/snapshots/'+ rec.data.time;
+ },
+ confirmMsg: function(rec) {
+ let me = this;
+ let time = rec.data.time;
+ return Ext.String.format(gettext('Are you sure you want to forget snapshot {0}'), `'${time}'`);
+ },
+ callback: 'reloadSnapshots',
+ },
+ ],
+ store: {
+ fields: ['time', 'size', 'ctime', 'encrypted'],
+ proxy: { type: 'proxmox' },
+ sorters: [
+ {
+ property: 'time',
+ direction: 'DESC',
+ },
+ ],
+ },
+ bind: {
+ title: Ext.String.format(gettext("Backup snapshots on '{0}'"), '{name}'),
+ hidden: '{!selected}',
+ },
+ columns: [
+ {
+ text: 'Time',
+ dataIndex: 'time',
+ flex: 1,
+ },
+ {
+ text: 'Size',
+ dataIndex: 'size',
+ flex: 1,
+ },
+ {
+ text: 'Encrypted',
+ dataIndex: 'encrypted',
+ renderer: Proxmox.Utils.format_boolean,
+ flex: 1,
+ },
+ ],
+ },
+ ],
+
+});
+
+Ext.define('pmg-pbs-config', {
+ extend: 'Ext.data.Model',
+ fields: ['remote', 'server', 'datastore', 'username', 'disabled'],
+ proxy: {
+ type: 'proxmox',
+ url: '/api2/json/config/pbs',
+ },
+ idProperty: 'remote',
+});
+
+Ext.define('PMG.PBSConfigGrid', {
+ extend: 'Ext.grid.GridPanel',
+ xtype: 'pmgPBSConfigGrid',
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ run_editor: function() {
+ let me = this;
+ let view = me.getView();
+ let rec = view.getSelection()[0];
+ if (!rec) {
+ return;
+ }
+
+ let win = Ext.createWidget('pmgPBSEdit', {
+ remoteId: rec.data.remote,
+ });
+ win.on('destroy', me.reload, me);
+ win.load();
+ win.show();
+ },
+
+ newRemote: function() {
+ let me = this;
+ let win = Ext.createWidget('pmgPBSEdit', {});
+ win.on('destroy', me.reload, me);
+ win.show();
+ },
+
+
+ reload: function() {
+ let me = this;
+ let view = me.getView();
+ view.getStore().load();
+ view.fireEvent('load', view);
+ },
+
+ createSchedule: function() {
+ let me = this;
+ let view = me.getView();
+ let rec = view.getSelection()[0];
+ let remotename = rec.data.remote;
+ let win = Ext.createWidget('pmgPBSScheduleEdit', {
+ remote: remotename,
+ });
+ win.on('destroy', me.reload, me);
+ win.show();
+ },
+
+ init: function(view) {
+ let me = this;
+ Proxmox.Utils.monStoreErrors(view, view.getStore(), true);
+ },
+
+ },
+
+ store: {
+ model: 'pmg-pbs-config',
+ sorters: [{
+ property: 'remote',
+ order: 'DESC',
+ }],
+ },
+
+ tbar: [
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Edit'),
+ disabled: true,
+ handler: 'run_editor',
+ },
+ {
+ text: gettext('Create'),
+ handler: 'newRemote',
+ },
+ {
+ xtype: 'proxmoxStdRemoveButton',
+ baseurl: '/config/pbs',
+ callback: 'reload',
+ },
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Schedule'),
+ enableFn: function(rec) {
+ return !rec.data.disable;
+ },
+ disabled: true,
+ handler: 'createSchedule',
+ },
+ {
+ xtype: 'proxmoxStdRemoveButton',
+ baseurl: '/nodes/' + Proxmox.NodeName + '/pbs/',
+ callback: 'reload',
+ text: gettext('Remove Schedule'),
+ confirmMsg: function(rec) {
+ let me = this;
+ let name = rec.getId();
+ return Ext.String.format(gettext('Are you sure you want to remove the schedule for {0}'), `'${name}'`);
+ },
+ getUrl: function(rec) {
+ let me = this;
+ return me.baseurl + '/' + rec.getId() + '/timer';
+ },
+ },
+ ],
+
+ listeners: {
+ itemdblclick: 'run_editor',
+ activate: 'reload',
+ },
+
+ columns: [
+ {
+ header: gettext('Backup Server name'),
+ sortable: true,
+ dataIndex: 'remote',
+ flex: 2,
+ },
+ {
+ header: gettext('Server'),
+ sortable: true,
+ dataIndex: 'server',
+ flex: 2,
+ },
+ {
+ header: gettext('Datastore'),
+ sortable: true,
+ dataIndex: 'datastore',
+ flex: 1,
+ },
+ {
+ header: gettext('User ID'),
+ sortable: true,
+ dataIndex: 'username',
+ flex: 1,
+ },
+ {
+ header: gettext('Encryption'),
+ width: 80,
+ sortable: true,
+ dataIndex: 'encryption-key',
+ renderer: Proxmox.Utils.format_boolean,
+ },
+ {
+ header: gettext('Enabled'),
+ width: 80,
+ sortable: true,
+ dataIndex: 'disable',
+ renderer: Proxmox.Utils.format_neg_boolean,
+ },
+ ],
+
+});
+
--
2.20.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
` (15 preceding siblings ...)
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 3/3] add PBSConfig tab to Backup menu Stoiko Ivanov
@ 2020-10-30 5:47 ` Dietmar Maurer
16 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 5:47 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
> changes RFC->v1:
> * moved the potentially reusable parts to pve-common (PBSTools.pm, and 2
> functions in Systemd.pm)
I would wait with that until we have a fully functional encryption
implementation in PVE.
IMHO, we do nor need encryption for PMG now.
> * added GUI support (mostly adapted from the LDAPConfig) - huge thanks to
> Dominik for his patience and help!
> * added support for encrypted backups
I would delay this (implement it on PVE first)
> * added support for pruning backups
We can do this after backup - no need for systemd timers.
> * added a systemd-timer, which runs a daily prune based on the configured
> settings
see above
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api 01/11] drop left-over commented out code
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 01/11] drop left-over commented out code Stoiko Ivanov
@ 2020-10-30 5:58 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 5:58 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api 02/11] Backup: split backup creation and creating tar
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 02/11] Backup: split backup creation and creating tar Stoiko Ivanov
@ 2020-10-30 6:20 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 6:20 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
applied - see comment inline
> On 10/28/2020 7:54 PM Stoiko Ivanov <s.ivanov@proxmox.com> wrote:
>
>
> In preparation for integrating PMG with PBS split the current creation of
> a PMG backup into 2 methods:
> * create all files in a backup in a target directory
> * create a tarball from a backup in a temporary directory
>
> use the changed method in the backup API call.
>
> Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
> ---
> src/PMG/API2/Backup.pm | 2 +-
> src/PMG/Backup.pm | 41 ++++++++++++++++++++++++++++++++---------
> 2 files changed, 33 insertions(+), 10 deletions(-)
>
> diff --git a/src/PMG/API2/Backup.pm b/src/PMG/API2/Backup.pm
> index 0bfcfc9..08c06b5 100644
> --- a/src/PMG/API2/Backup.pm
> +++ b/src/PMG/API2/Backup.pm
> @@ -131,7 +131,7 @@ __PACKAGE__->register_method ({
>
> print "starting backup to: $filename\n";
>
> - PMG::Backup::pmg_backup($filename, $param->{statistic});
> + PMG::Backup::pmg_backup_pack($filename, $param->{statistic});
>
> print "backup finished\n";
>
> diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm
> index 025bac2..40c41f3 100644
> --- a/src/PMG/Backup.pm
> +++ b/src/PMG/Backup.pm
> @@ -133,10 +133,11 @@ sub dumpstatdb {
> }
>
> sub pmg_backup {
> - my ($filename, $include_statistics) = @_;
> + my ($dirname, $include_statistics) = @_;
> +
> + die "No backupdir provided!\n" if !defined($dirname);
>
> my $time = time;
> - my $dirname = "/tmp/proxbackup_$$.$time";
> my $dbfn = "Proxmox_ruledb.sql";
> my $statfn = "Proxmox_statdb.sql";
> my $tarfn = "config_backup.tar";
> @@ -145,12 +146,7 @@ sub pmg_backup {
>
> eval {
>
> - my $targetdir = dirname($filename);
> - mkdir $targetdir; # try to create target dir
> - -d $targetdir ||
> - die "unable to access target directory '$targetdir'\n";
> -
> - # create a temporary directory
> + # create backup directory
> mkdir $dirname;
seems we do this twice now
>
> # dump the database first
> @@ -197,7 +193,34 @@ sub pmg_backup {
> 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 ||
> + };
> + my $err = $@;
> +
> + if ($err) {
> + die $err;
> + }
> +}
> +
> +sub pmg_backup_pack {
> + my ($filename, $include_statistics) = @_;
> +
> + my $time = time;
> + my $dirname = "/tmp/proxbackup_$$.$time";
> +
> + eval {
> +
> + my $targetdir = dirname($filename);
> + mkdir $targetdir; # try to create target dir
> + -d $targetdir ||
> + die "unable to access target directory '$targetdir'\n";
> +
> + rmtree $dirname;
> + # create backup directory
> + mkdir $dirname;
> +
> + pmg_backup($dirname, $include_statistics);
> +
> + system("rm -f $filename; tar czf $filename --strip-components=1 -C $dirname .") == 0 ||
> die "unable to create backup archive: ERROR";
> };
> my $err = $@;
> --
> 2.20.1
>
>
>
> _______________________________________________
> pmg-devel mailing list
> pmg-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pmg-devel
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api 03/11] Restore: optionally restore from directory
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 03/11] Restore: optionally restore from directory Stoiko Ivanov
@ 2020-10-30 6:26 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 6:26 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
applied
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup Stoiko Ivanov
@ 2020-10-30 6:32 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 6:32 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
applied
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client Stoiko Ivanov
@ 2020-10-30 6:33 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 6:33 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
applied
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs Stoiko Ivanov
@ 2020-10-30 6:38 ` Dietmar Maurer
0 siblings, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 6:38 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
> This commit needs a versioned dependency on pve-common
As menitioned before, I would like to have a fully functional encryption
implementation in PVE before we implement it here.
^ permalink raw reply [flat|nested] 26+ messages in thread
* [pmg-devel] applied: [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
@ 2020-10-30 8:26 ` Dietmar Maurer
2020-11-10 8:34 ` [pmg-devel] " Thomas Lamprecht
1 sibling, 0 replies; 26+ messages in thread
From: Dietmar Maurer @ 2020-10-30 8:26 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
applied
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
2020-10-30 8:26 ` [pmg-devel] applied: " Dietmar Maurer
@ 2020-11-10 8:34 ` Thomas Lamprecht
1 sibling, 0 replies; 26+ messages in thread
From: Thomas Lamprecht @ 2020-11-10 8:34 UTC (permalink / raw)
To: Stoiko Ivanov, pmg-devel
On 28.10.20 19:54, Stoiko Ivanov wrote:
> taken from pve-storage/PVE/API2/Disks/Directory.pm (and made available as
> public sub)
we could now replace those usages over there? Not urgent, just as reminder.
>
> Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
> ---
> refactoring pve-storage to use this (and drop the now duplicated code) will
> be done separately
>
> src/PVE/Systemd.pm | 72 ++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 72 insertions(+)
>
> diff --git a/src/PVE/Systemd.pm b/src/PVE/Systemd.pm
> index 85b35a3..bcba5eb 100644
> --- a/src/PVE/Systemd.pm
> +++ b/src/PVE/Systemd.pm
> @@ -7,6 +7,8 @@ use Net::DBus qw(dbus_uint32 dbus_uint64);
> use Net::DBus::Callback;
> use Net::DBus::Reactor;
>
> +use PVE::Tools qw(file_set_contents file_get_contents trim);
> +
> sub escape_unit {
> my ($val, $is_path) = @_;
>
> @@ -163,4 +165,74 @@ sub wait_for_unit_removed($;$) {
> }, $timeout);
> }
>
> +sub read_ini {
> + my ($filename) = @_;
> +
> + my $content = file_get_contents($filename);
> + my @lines = split /\n/, $content;
> +
> + my $result = {};
> + my $section;
> +
> + foreach my $line (@lines) {
> + $line = trim($line);
> + if ($line =~ m/^\[([^\]]+)\]/) {
> + $section = $1;
> + if (!defined($result->{$section})) {
> + $result->{$section} = {};
> + }
> + } elsif ($line =~ m/^(.*?)=(.*)$/) {
> + my ($key, $val) = ($1, $2);
> + if (!$section) {
> + warn "key value pair found without section, skipping\n";
> + next;
> + }
> +
> + if ($result->{$section}->{$key}) {
> + # make duplicate properties to arrays to keep the order
> + my $prop = $result->{$section}->{$key};
> + if (ref($prop) eq 'ARRAY') {
> + push @$prop, $val;
> + } else {
> + $result->{$section}->{$key} = [$prop, $val];
> + }
> + } else {
> + $result->{$section}->{$key} = $val;
> + }
> + }
> + # ignore everything else
> + }
> +
> + return $result;
> +};
> +
> +sub write_ini {
> + my ($ini, $filename) = @_;
> +
> + my $content = "";
> +
> + foreach my $sname (sort keys %$ini) {
> + my $section = $ini->{$sname};
> +
> + $content .= "[$sname]\n";
> +
> + foreach my $pname (sort keys %$section) {
> + my $prop = $section->{$pname};
> +
> + if (!ref($prop)) {
> + $content .= "$pname=$prop\n";
> + } elsif (ref($prop) eq 'ARRAY') {
> + foreach my $val (@$prop) {
> + $content .= "$pname=$val\n";
> + }
> + } else {
> + die "invalid property '$pname'\n";
> + }
> + }
> + $content .= "\n";
> + }
> +
> + file_set_contents($filename, $content);
> +};
> +
> 1;
>
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2020-11-10 8:35 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-28 18:54 [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 1/2] Systemd: add helpers for parsing unit files Stoiko Ivanov
2020-10-30 8:26 ` [pmg-devel] applied: " Dietmar Maurer
2020-11-10 8:34 ` [pmg-devel] " Thomas Lamprecht
2020-10-28 18:54 ` [pmg-devel] [PATCH pve-common 2/2] add helper module for handling PBS Integration Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 01/11] drop left-over commented out code Stoiko Ivanov
2020-10-30 5:58 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 02/11] Backup: split backup creation and creating tar Stoiko Ivanov
2020-10-30 6:20 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 03/11] Restore: optionally restore from directory Stoiko Ivanov
2020-10-30 6:26 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 04/11] Backup: push restore options to PMG::Backup Stoiko Ivanov
2020-10-30 6:32 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 05/11] debian: add dependency on proxmox-backup-client Stoiko Ivanov
2020-10-30 6:33 ` [pmg-devel] applied: " Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 06/11] add initial SectionConfig for pbs Stoiko Ivanov
2020-10-30 6:38 ` Dietmar Maurer
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 07/11] Add API2 module for PBS configuration Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 08/11] Add API2 module for per-node backups to PBS Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 09/11] pbs-integration: add CLI calls to pmgbackup Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 10/11] add scheduled backup to PBS remotes Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-api 11/11] add daily timer for pruning configured remotes Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 1/3] Make Backup/Restore panel a menuentry Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 2/3] refactor RestoreWindow for PBS Stoiko Ivanov
2020-10-28 18:54 ` [pmg-devel] [PATCH pmg-gui 3/3] add PBSConfig tab to Backup menu Stoiko Ivanov
2020-10-30 5:47 ` [pmg-devel] [PATCH pve-common/api/gui] add initial PBS integration Dietmar Maurer
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal