From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 043106524C for ; Mon, 2 Nov 2020 19:46:37 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id EF5CE25526 for ; Mon, 2 Nov 2020 19:46:06 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id E494E254B9 for ; Mon, 2 Nov 2020 19:46:01 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 922AF46010 for ; Mon, 2 Nov 2020 19:46:01 +0100 (CET) From: Stoiko Ivanov To: pmg-devel@lists.proxmox.com Date: Mon, 2 Nov 2020 19:45:34 +0100 Message-Id: <20201102184538.17127-8-s.ivanov@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201102184538.17127-1-s.ivanov@proxmox.com> References: <20201102184538.17127-1-s.ivanov@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.274 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_SBL 0.644 Contains an URL's NS IP listed in the Spamhaus SBL blocklist [backup.pm] URIBL_SBL_A 0.1 Contains URL's A record listed in the Spamhaus SBL blocklist [backup.pm] Subject: [pmg-devel] [PATCH pmg-api v2 6/7] add scheduled backup to PBS remotes X-BeenThere: pmg-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 02 Nov 2020 18:46:37 -0000 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 --- debian/pmg-pbsbackup@.service | 6 ++ debian/rules | 1 + src/Makefile | 3 +- src/PMG/API2/PBS/Job.pm | 130 ++++++++++++++++++++++++++++++++++ src/PMG/CLI/pmgbackup.pm | 6 ++ src/PMG/PBSSchedule.pm | 104 +++++++++++++++++++++++++++ 6 files changed, 249 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 dee1754..4b686ec 100644 --- a/src/PMG/API2/PBS/Job.pm +++ b/src/PMG/API2/PBS/Job.pm @@ -14,6 +14,7 @@ use PVE::PBSClient; use PMG::RESTEnvironment; use PMG::Backup; use PMG::PBSConfig; +use PMG::PBSSchedule; use base qw(PVE::RESTHandler); @@ -368,4 +369,133 @@ __PACKAGE__->register_method ({ return $rpcenv->fork_worker('pbs_restore', 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 c422927..950f6e4 100644 --- a/src/PMG/CLI/pmgbackup.pm +++ b/src/PMG/CLI/pmgbackup.pm @@ -54,6 +54,12 @@ our $cmddef = { 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} ], + 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