mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-04-29 08:27:29 +00:00

Allows sufficiently privileged users to pass-in retention and performance parameters for manual backup, but keeps tmpdir, dumpdir and script root-only. Such users could already edit the job accordingly, so essentially not granting anything new. Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com> Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
302 lines
8.3 KiB
Perl
302 lines
8.3 KiB
Perl
package PVE::API2::VZDump;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use PVE::AccessControl;
|
|
use PVE::Cluster;
|
|
use PVE::Exception qw(raise_param_exc);
|
|
use PVE::INotify;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
use PVE::RPCEnvironment;
|
|
use PVE::Storage;
|
|
use PVE::Tools qw(extract_param);
|
|
use PVE::VZDump::Common;
|
|
use PVE::VZDump;
|
|
|
|
use PVE::API2::Backup;
|
|
use PVE::API2Tools;
|
|
|
|
use Data::Dumper; # fixme: remove
|
|
|
|
use base qw(PVE::RESTHandler);
|
|
|
|
my sub assert_param_permission_vzdump {
|
|
my ($rpcenv, $user, $param) = @_;
|
|
return if $user eq 'root@pam'; # always OK
|
|
|
|
PVE::API2::Backup::assert_param_permission_common($rpcenv, $user, $param);
|
|
|
|
if (!$param->{dumpdir} && (defined($param->{maxfiles}) || defined($param->{'prune-backups'}))) {
|
|
my $storeid = $param->{storage} || 'local';
|
|
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
|
|
} # no else branch, because dumpdir is root-only
|
|
}
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'vzdump',
|
|
path => '',
|
|
method => 'POST',
|
|
description => "Create backup.",
|
|
permissions => {
|
|
description => "The user needs 'VM.Backup' permissions on any VM, and "
|
|
."'Datastore.AllocateSpace' on the backup storage. The 'tmpdir', 'dumpdir' and "
|
|
."'script' parameters are restricted to the 'root\@pam' user. The 'maxfiles' and "
|
|
."'prune-backups' settings require 'Datastore.Allocate' on the backup storage. The "
|
|
."'bwlimit', 'performance' and 'ionice' parameters require 'Sys.Modify' on '/'.",
|
|
user => 'all',
|
|
},
|
|
protected => 1,
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => PVE::VZDump::Common::json_config_properties({
|
|
stdout => {
|
|
type => 'boolean',
|
|
description => "Write tar to stdout, not to a file.",
|
|
optional => 1,
|
|
},
|
|
}),
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
|
|
my $user = $rpcenv->get_user();
|
|
|
|
my $nodename = PVE::INotify::nodename();
|
|
|
|
if ($rpcenv->{type} ne 'cli') {
|
|
raise_param_exc({ node => "option is only allowed on the command line interface."})
|
|
if $param->{node} && $param->{node} ne $nodename;
|
|
|
|
raise_param_exc({ stdout => "option is only allowed on the command line interface."})
|
|
if $param->{stdout};
|
|
}
|
|
|
|
assert_param_permission_vzdump($rpcenv, $user, $param);
|
|
|
|
PVE::VZDump::verify_vzdump_parameters($param, 1);
|
|
|
|
# silent exit if we run on wrong node
|
|
return 'OK' if $param->{node} && $param->{node} ne $nodename;
|
|
|
|
my $cmdline = PVE::VZDump::Common::command_line($param);
|
|
|
|
my $vmids_per_node = PVE::VZDump::get_included_guests($param);
|
|
|
|
my $local_vmids = delete $vmids_per_node->{$nodename} // [];
|
|
|
|
# include IDs for deleted guests, and visibly fail later
|
|
my $orphaned_vmids = delete $vmids_per_node->{''} // [];
|
|
push @{$local_vmids}, @{$orphaned_vmids};
|
|
|
|
my $skiplist = [ map { @$_ } values $vmids_per_node->%* ];
|
|
|
|
if($param->{stop}){
|
|
PVE::VZDump::stop_running_backups();
|
|
return 'OK' if !scalar(@{$local_vmids});
|
|
}
|
|
|
|
# silent exit if specified VMs run on other nodes
|
|
return "OK" if !scalar(@{$local_vmids}) && !$param->{all};
|
|
|
|
PVE::VZDump::parse_mailto_exclude_path($param);
|
|
|
|
die "you can only backup a single VM with option --stdout\n"
|
|
if $param->{stdout} && scalar(@{$local_vmids}) != 1;
|
|
|
|
# If the root-only dumpdir is used rather than a storage, the check will succeed anyways.
|
|
my $storeid = $param->{storage} || 'local';
|
|
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]);
|
|
|
|
my $worker = sub {
|
|
my $upid = shift;
|
|
|
|
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
|
die "interrupted by signal\n";
|
|
};
|
|
|
|
$param->{vmids} = $local_vmids;
|
|
my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist);
|
|
|
|
my $LOCK_FH = eval {
|
|
$vzdump->getlock($upid); # only one process allowed
|
|
};
|
|
if (my $err = $@) {
|
|
$vzdump->sendmail([], 0, $err);
|
|
exit(-1);
|
|
}
|
|
|
|
if (defined($param->{ionice})) {
|
|
if ($param->{ionice} > 7) {
|
|
PVE::VZDump::run_command(undef, "ionice -c3 -p $$");
|
|
} else {
|
|
PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$");
|
|
}
|
|
}
|
|
$vzdump->exec_backup($rpcenv, $user);
|
|
|
|
close($LOCK_FH);
|
|
};
|
|
|
|
open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout};
|
|
open STDERR, '>/dev/null' if $param->{quiet};
|
|
|
|
if ($rpcenv->{type} eq 'cli') {
|
|
if ($param->{stdout}) {
|
|
|
|
open my $saved_stdout, ">&STDOUT"
|
|
|| die "can't dup STDOUT: $!\n";
|
|
|
|
open STDOUT, '>&STDERR' ||
|
|
die "unable to redirect STDOUT: $!\n";
|
|
|
|
$param->{stdout} = $saved_stdout;
|
|
}
|
|
}
|
|
|
|
my $taskid;
|
|
$taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1;
|
|
|
|
return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker);
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'defaults',
|
|
path => 'defaults',
|
|
method => 'GET',
|
|
description => "Get the currently configured vzdump defaults.",
|
|
permissions => {
|
|
description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " .
|
|
"permissions for the specified storage (or default storage if none specified). Some " .
|
|
"properties are only returned when the user has 'Sys.Audit' permissions for the node.",
|
|
user => 'all',
|
|
},
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
storage => get_standard_option('pve-storage-id', { optional => 1 }),
|
|
},
|
|
},
|
|
returns => {
|
|
type => 'object',
|
|
additionalProperties => 0,
|
|
properties => PVE::VZDump::Common::json_config_properties(),
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $node = extract_param($param, 'node');
|
|
my $storage = extract_param($param, 'storage');
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $authuser = $rpcenv->get_user();
|
|
|
|
my $res = PVE::VZDump::read_vzdump_defaults();
|
|
|
|
$res->{storage} = $storage if defined($storage);
|
|
|
|
if (!defined($res->{dumpdir}) && !defined($res->{storage})) {
|
|
$res->{storage} = 'local';
|
|
}
|
|
|
|
if (defined($res->{storage})) {
|
|
$rpcenv->check_any(
|
|
$authuser,
|
|
"/storage/$res->{storage}",
|
|
['Datastore.Audit', 'Datastore.AllocateSpace'],
|
|
);
|
|
|
|
my $info = PVE::VZDump::storage_info($res->{storage});
|
|
for my $key (qw(dumpdir prune-backups)) {
|
|
$res->{$key} = $info->{$key} if defined($info->{$key});
|
|
}
|
|
}
|
|
|
|
if (defined($res->{'prune-backups'})) {
|
|
$res->{'prune-backups'} = PVE::JSONSchema::print_property_string(
|
|
$res->{'prune-backups'},
|
|
'prune-backups',
|
|
);
|
|
}
|
|
|
|
$res->{mailto} = join(",", @{$res->{mailto}})
|
|
if defined($res->{mailto});
|
|
|
|
$res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}})
|
|
if defined($res->{'exclude-path'});
|
|
|
|
# normal backup users don't need to know these
|
|
if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) {
|
|
delete $res->{mailto};
|
|
delete $res->{tmpdir};
|
|
delete $res->{dumpdir};
|
|
delete $res->{script};
|
|
delete $res->{ionice};
|
|
}
|
|
|
|
my $pool = $res->{pool};
|
|
if (defined($pool) &&
|
|
!$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) {
|
|
delete $res->{pool};
|
|
}
|
|
|
|
return $res;
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'extractconfig',
|
|
path => 'extractconfig',
|
|
method => 'GET',
|
|
description => "Extract configuration from vzdump backup archive.",
|
|
permissions => {
|
|
description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
|
|
user => 'all',
|
|
},
|
|
protected => 1,
|
|
proxyto => 'node',
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
volume => {
|
|
description => "Volume identifier",
|
|
type => 'string',
|
|
completion => \&PVE::Storage::complete_volume,
|
|
},
|
|
},
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $volume = extract_param($param, 'volume');
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $authuser = $rpcenv->get_user();
|
|
|
|
my $storage_cfg = PVE::Storage::config();
|
|
PVE::Storage::check_volume_access(
|
|
$rpcenv,
|
|
$authuser,
|
|
$storage_cfg,
|
|
undef,
|
|
$volume,
|
|
'backup',
|
|
);
|
|
|
|
if (PVE::Storage::parse_volume_id($volume, 1)) {
|
|
my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume);
|
|
$rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
|
|
}
|
|
|
|
return PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
|
|
}});
|
|
|
|
1;
|