]>
git.proxmox.com Git - pve-common.git/blob - src/PVE/PBSClient.pm
1d9a9f4c9b484f850e95269a532ad057f026c70b
1 package PVE
::PBSClient
;
3 # utility functions for interaction with Proxmox Backup Server
7 use Fcntl
qw(F_GETFD F_SETFD FD_CLOEXEC);
10 use POSIX
qw(strftime ENOENT);
12 use PVE
::Tools
qw(run_command file_set_contents file_get_contents file_read_firstline);
13 use PVE
::JSONSchema
qw(get_standard_option);
16 my ($class, $scfg, $storeid, $sdir) = @_;
18 die "no section config provided\n" if ref($scfg) eq '';
19 die "undefined store id\n" if !defined($storeid);
21 my $secret_dir = $sdir // '/etc/pve/priv/storage';
23 my $self = bless { scfg
=> $scfg, storeid
=> $storeid, secret_dir
=> $secret_dir }, $class;
26 my sub password_file_name
{
29 return "$self->{secret_dir}/$self->{storeid}.pw";
33 my ($self, $password) = @_;
35 my $pwfile = password_file_name
($self);
36 mkdir $self->{secret_dir
};
38 PVE
::Tools
::file_set_contents
($pwfile, "$password\n", 0600);
44 my $pwfile = password_file_name
($self);
52 my $pwfile = password_file_name
($self);
54 return PVE
::Tools
::file_read_firstline
($pwfile);
57 sub encryption_key_file_name
{
60 return "$self->{secret_dir}/$self->{storeid}.enc";
63 sub set_encryption_key
{
64 my ($self, $key) = @_;
66 my $encfile = encryption_key_file_name
($self);
67 mkdir $self->{secret_dir
};
69 PVE
::Tools
::file_set_contents
($encfile, "$key\n", 0600);
72 sub delete_encryption_key
{
75 my $encfile = encryption_key_file_name
($self);
77 if (!unlink $encfile) {
78 return if $! == ENOENT
;
79 die "failed to delete encryption key! $!\n";
83 # Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
84 my sub open_encryption_key
{
87 my $encryption_key_file = encryption_key_file_name
($self);
90 if (!open($keyfd, '<', $encryption_key_file)) {
91 return undef if $! == ENOENT
;
92 die "failed to open encryption key: $encryption_key_file: $!\n";
98 my $USE_CRYPT_PARAMS = {
104 my sub do_raw_client_cmd
{
105 my ($self, $client_cmd, $param, %opts) = @_;
107 my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
109 my $client_exe = '/usr/bin/proxmox-backup-client';
110 die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
113 my $scfg = $self->{scfg
};
114 my $server = $scfg->{server
};
115 my $datastore = $scfg->{datastore
};
116 my $username = $scfg->{username
} // 'root@pam';
118 my $userns_cmd = delete $opts{userns_cmd
};
122 push @$cmd, @$userns_cmd if defined($userns_cmd);
124 push @$cmd, $client_exe, $client_cmd;
126 # This must live in the top scope to not get closed before the `run_command`
129 if (defined($keyfd = open_encryption_key
($self))) {
130 my $flags = fcntl($keyfd, F_GETFD
, 0)
131 // die "failed to get file descriptor flags: $!\n";
132 fcntl($keyfd, F_SETFD
, $flags & ~FD_CLOEXEC
)
133 or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
134 push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
136 push @$cmd, '--crypt-mode=none';
140 push @$cmd, @$param if defined($param);
142 push @$cmd, "--repository", "$username\@$server:$datastore";
144 local $ENV{PBS_PASSWORD
} = get_password
($self);
146 local $ENV{PBS_FINGERPRINT
} = $scfg->{fingerprint
};
148 # no ascii-art on task logs
149 local $ENV{PROXMOX_OUTPUT_NO_BORDER
} = 1;
150 local $ENV{PROXMOX_OUTPUT_NO_HEADER
} = 1;
152 if (my $logfunc = $opts{logfunc
}) {
153 $logfunc->("run: " . join(' ', @$cmd));
156 run_command
($cmd, %opts);
159 my sub run_raw_client_cmd
{
160 my ($self, $client_cmd, $param, %opts) = @_;
161 return do_raw_client_cmd
($self, $client_cmd, $param, %opts);
164 my sub run_client_cmd
{
165 my ($self, $client_cmd, $param, $no_output) = @_;
168 my $outfunc = sub { $json_str .= "$_[0]\n" };
170 $param = [] if !defined($param);
171 $param = [ $param ] if !ref($param);
173 $param = [@$param, '--output-format=json'] if !$no_output;
175 do_raw_client_cmd
($self, $client_cmd, $param,
176 outfunc
=> $outfunc, errmsg
=> 'proxmox-backup-client failed');
178 return undef if $no_output;
180 my $res = decode_json
($json_str);
185 sub autogen_encryption_key
{
187 my $encfile = encryption_key_file_name
($self);
188 run_command
(['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile]);
192 my ($self, $opts) = @_;
195 if (defined($opts->{group
})) {
196 push @$param, $opts->{group
};
199 return run_client_cmd
($self, "snapshots", $param);
203 my ($self, $opts) = @_;
205 my $type = delete $opts->{type
};
206 die "backup-type not provided\n" if !defined($type);
207 my $id = delete $opts->{id
};
208 die "backup-id not provided\n" if !defined($id);
209 my $root = delete $opts->{root
};
210 die "root dir not provided\n" if !defined($root);
211 my $pxarname = delete $opts->{pxarname
};
212 die "archive name not provided\n" if !defined($pxarname);
213 my $time = delete $opts->{time};
217 push @$param, "$pxarname.pxar:$root";
218 push @$param, '--backup-type', $type;
219 push @$param, '--backup-id', $id;
220 push @$param, '--backup-time', $time if defined($time);
222 return run_raw_client_cmd
($self, 'backup', $param, %$opts);
226 my ($self, $opts) = @_;
228 my $snapshot = delete $opts->{snapshot
};
229 die "snapshot not provided\n" if !defined($snapshot);
230 my $pxarname = delete $opts->{pxarname
};
231 die "archive name not provided\n" if !defined($pxarname);
232 my $target = delete $opts->{target
};
233 die "restore-target not provided\n" if !defined($target);
234 #my $time = delete $opts->{time};
238 push @$param, "$snapshot";
239 push @$param, "$pxarname.pxar";
240 push @$param, "$target";
241 push @$param, "--allow-existing-dirs", 0;
243 return run_raw_client_cmd
($self, 'restore', $param, %$opts);
246 sub forget_snapshot
{
247 my ($self, $snapshot) = @_;
249 die "snapshot not provided\n" if !defined($snapshot);
253 push @$param, "$snapshot";
255 return run_raw_client_cmd
($self, 'forget', $param);
259 my ($self, $opts, $prune_opts, $group) = @_;
261 die "group not provided\n" if !defined($group);
263 # do nothing if no keep options specified for remote
264 return [] if scalar(keys %$prune_opts) == 0;
268 push @$param, "--quiet";
270 if (defined($opts->{'dry-run'}) && $opts->{'dry-run'}) {
271 push @$param, "--dry-run", $opts->{'dry-run'};
274 foreach my $keep_opt (keys %$prune_opts) {
275 push @$param, "--$keep_opt", $prune_opts->{$keep_opt};
277 push @$param, "$group";
279 return run_client_cmd
($self, 'prune', $param);
291 my $res = run_client_cmd
($self, "status");
294 $total = $res->{total
};
295 $used = $res->{used
};
296 $free = $res->{avail
};
302 return ($total, $free, $used, $active);