]>
git.proxmox.com Git - pve-common.git/blob - src/PVE/PBSClient.pm
16f77656f9b8cda4b6aff81740be7ecce7f2ea3a
1 package PVE
::PBSClient
;
2 # utility functions for interaction with Proxmox Backup client CLI executable
7 use Fcntl
qw(F_GETFD F_SETFD FD_CLOEXEC);
10 use POSIX
qw(strftime ENOENT);
12 use PVE
::JSONSchema
qw(get_standard_option);
13 use PVE
::Tools
qw(run_command file_set_contents file_get_contents file_read_firstline $IPV6RE);
15 # returns a repository string suitable for proxmox-backup-client, pbs-restore, etc.
16 # $scfg must have the following structure:
20 # port (optional defaults to 8007)
21 # username (optional defaults to 'root@pam')
26 my $server = $scfg->{server
};
27 die "no server given\n" if !defined($server);
29 $server = "[$server]" if $server =~ /^$IPV6RE$/;
31 if (my $port = $scfg->{port
}) {
32 $server .= ":$port" if $port != 8007;
35 my $datastore = $scfg->{datastore
};
36 die "no datastore given\n" if !defined($datastore);
38 my $username = $scfg->{username
} // 'root@pam';
40 return "$username\@$server:$datastore";
44 my ($class, $scfg, $storeid, $sdir) = @_;
46 die "no section config provided\n" if ref($scfg) eq '';
47 die "undefined store id\n" if !defined($storeid);
49 my $secret_dir = $sdir // '/etc/pve/priv/storage';
54 secret_dir
=> $secret_dir
59 my sub password_file_name
{
62 return "$self->{secret_dir}/$self->{storeid}.pw";
66 my ($self, $password) = @_;
68 my $pwfile = password_file_name
($self);
69 mkdir $self->{secret_dir
};
71 PVE
::Tools
::file_set_contents
($pwfile, "$password\n", 0600);
77 my $pwfile = password_file_name
($self);
79 unlink $pwfile or die "deleting password file failed - $!\n";
85 my $pwfile = password_file_name
($self);
87 return PVE
::Tools
::file_read_firstline
($pwfile);
90 sub encryption_key_file_name
{
93 return "$self->{secret_dir}/$self->{storeid}.enc";
96 sub set_encryption_key
{
97 my ($self, $key) = @_;
99 my $encfile = $self->encryption_key_file_name();
100 mkdir $self->{secret_dir
};
102 PVE
::Tools
::file_set_contents
($encfile, "$key\n", 0600);
105 sub delete_encryption_key
{
108 my $encfile = $self->encryption_key_file_name();
110 if (!unlink $encfile) {
111 return if $! == ENOENT
;
112 die "failed to delete encryption key! $!\n";
116 # Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
117 my sub open_encryption_key
{
120 my $encryption_key_file = $self->encryption_key_file_name();
123 if (!open($keyfd, '<', $encryption_key_file)) {
124 return undef if $! == ENOENT
;
125 die "failed to open encryption key: $encryption_key_file: $!\n";
131 my $USE_CRYPT_PARAMS = {
137 my sub do_raw_client_cmd
{
138 my ($self, $client_cmd, $param, %opts) = @_;
140 my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
142 my $client_exe = (delete $opts{binary
}) || 'proxmox-backup-client';
143 $client_exe = "/usr/bin/$client_exe";
144 die "executable not found '$client_exe'! proxmox-backup-client or proxmox-backup-file-restore not installed?\n"
147 my $scfg = $self->{scfg
};
148 my $repo = get_repository
($scfg);
150 my $userns_cmd = delete $opts{userns_cmd
};
154 push @$cmd, @$userns_cmd if defined($userns_cmd);
156 push @$cmd, $client_exe, $client_cmd;
158 # This must live in the top scope to not get closed before the `run_command`
161 if (defined($keyfd = open_encryption_key
($self))) {
162 my $flags = fcntl($keyfd, F_GETFD
, 0)
163 // die "failed to get file descriptor flags: $!\n";
164 fcntl($keyfd, F_SETFD
, $flags & ~FD_CLOEXEC
)
165 or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
166 push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
168 push @$cmd, '--crypt-mode=none';
172 push @$cmd, @$param if defined($param);
174 push @$cmd, "--repository", $repo;
176 local $ENV{PBS_PASSWORD
} = $self->get_password();
178 local $ENV{PBS_FINGERPRINT
} = $scfg->{fingerprint
};
180 # no ascii-art on task logs
181 local $ENV{PROXMOX_OUTPUT_NO_BORDER
} = 1;
182 local $ENV{PROXMOX_OUTPUT_NO_HEADER
} = 1;
184 if (my $logfunc = $opts{logfunc
}) {
185 $logfunc->("run: " . join(' ', @$cmd));
188 run_command
($cmd, %opts);
191 my sub run_raw_client_cmd
{
192 my ($self, $client_cmd, $param, %opts) = @_;
193 return do_raw_client_cmd
($self, $client_cmd, $param, %opts);
196 my sub run_client_cmd
{
197 my ($self, $client_cmd, $param, $no_output, $binary) = @_;
200 my $outfunc = sub { $json_str .= "$_[0]\n" };
202 $binary //= 'proxmox-backup-client';
204 $param = [] if !defined($param);
205 $param = [ $param ] if !ref($param);
207 $param = [@$param, '--output-format=json'] if !$no_output;
214 errmsg
=> "$binary failed",
218 return undef if $no_output;
220 my $res = decode_json
($json_str);
225 sub autogen_encryption_key
{
227 my $encfile = $self->encryption_key_file_name();
229 ['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile],
230 errmsg
=> 'failed to create encryption key'
232 return file_get_contents
($encfile);
235 # lists all snapshots, optionally limited to a specific group
237 my ($self, $group) = @_;
240 push @$param, $group if defined($group);
242 return run_client_cmd
($self, "snapshots", $param);
245 # create a new PXAR backup of a FS directory tree - doesn't cross FS boundary
248 my ($self, $root, $id, $pxarname, $cmd_opts) = @_;
250 die "backup-id not provided\n" if !defined($id);
251 die "backup root dir not provided\n" if !defined($root);
252 die "archive name not provided\n" if !defined($pxarname);
255 "$pxarname.pxar:$root",
256 '--backup-type', 'host',
262 return run_raw_client_cmd
($self, 'backup', $param, %$cmd_opts);
266 my ($self, $snapshot, $pxarname, $target, $cmd_opts) = @_;
268 die "snapshot not provided\n" if !defined($snapshot);
269 die "archive name not provided\n" if !defined($pxarname);
270 die "restore-target not provided\n" if !defined($target);
276 "--allow-existing-dirs", 0,
280 return run_raw_client_cmd
($self, 'restore', $param, %$cmd_opts);
283 sub forget_snapshot
{
284 my ($self, $snapshot) = @_;
286 die "snapshot not provided\n" if !defined($snapshot);
288 return run_raw_client_cmd
($self, 'forget', ["$snapshot"]);
292 my ($self, $opts, $prune_opts, $group) = @_;
294 die "group not provided\n" if !defined($group);
296 # do nothing if no keep options specified for remote
297 return [] if scalar(keys %$prune_opts) == 0;
301 push @$param, "--quiet";
303 if (defined($opts->{'dry-run'}) && $opts->{'dry-run'}) {
304 push @$param, "--dry-run", $opts->{'dry-run'};
307 foreach my $keep_opt (keys %$prune_opts) {
308 push @$param, "--$keep_opt", $prune_opts->{$keep_opt};
310 push @$param, "$group";
312 return run_client_cmd
($self, 'prune', $param);
324 my $res = run_client_cmd
($self, "status");
327 $total = $res->{total
};
328 $used = $res->{used
};
329 $free = $res->{avail
};
335 return ($total, $free, $used, $active);