From: Stoiko Ivanov Date: Mon, 16 Nov 2020 11:01:14 +0000 (+0100) Subject: add scheduled backup to PBS remotes X-Git-Url: https://git.proxmox.com/?a=commitdiff_plain;h=7251cc51c105c9fb64522de98fb5ea8168a9ef08;p=pmg-api.git add scheduled backup to PBS remotes 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 --- 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 228f5ab..e40da25 100644 --- a/src/PMG/CLI/pmgbackup.pm +++ b/src/PMG/CLI/pmgbackup.pm @@ -85,6 +85,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;