[pmg-devel] [RFC pmg-api 06/12] add helper module for handling PBS Integration
Stoiko Ivanov
s.ivanov at proxmox.com
Mon Oct 19 21:02:03 CEST 2020
PBSTools.pm contains methods which eventually should be shared between
PVE and PMG, for:
* handling (sensitive) config-information (passwords, encryption keys)
* running PBS operations
Signed-off-by: Stoiko Ivanov <s.ivanov at proxmox.com>
---
src/Makefile | 1 +
src/PMG/PBSTools.pm | 239 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 240 insertions(+)
create mode 100644 src/PMG/PBSTools.pm
diff --git a/src/Makefile b/src/Makefile
index 05d9598..7f9726b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -66,6 +66,7 @@ LIBSOURCES = \
PMG/SMTP.pm \
PMG/Unpack.pm \
PMG/Backup.pm \
+ PMG/PBSTools.pm \
PMG/RuleCache.pm \
PMG/Statistic.pm \
PMG/UserConfig.pm \
diff --git a/src/PMG/PBSTools.pm b/src/PMG/PBSTools.pm
new file mode 100644
index 0000000..e2a2b60
--- /dev/null
+++ b/src/PMG/PBSTools.pm
@@ -0,0 +1,239 @@
+package PMG::PBSTools;
+
+# utility functions to talk with Proxmox Backup Server
+
+use strict;
+use warnings;
+# FIXME: cleanup to only needed modules:
+use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
+use HTTP::Request;
+use IO::File;
+use JSON;
+use LWP::UserAgent;
+use POSIX qw(strftime ENOENT);
+
+use PVE::Tools qw(run_command file_set_contents file_get_contents file_read_firstline trim dir_glob_foreach);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Systemd;
+
+# Helpers
+my $secret_dir;
+
+sub set_secret_dir {
+ my ($dir) = @_;
+ $secret_dir = $dir;
+}
+
+sub pbs_password_file_name {
+ my ($scfg, $storeid) = @_;
+
+ return "${secret_dir}/${storeid}.pw";
+}
+
+sub pbs_set_password {
+ my ($scfg, $storeid, $password) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($pwfile, "$password\n", 0600);
+}
+
+sub pbs_delete_password {
+ my ($scfg, $storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+
+ unlink $pwfile;
+}
+
+sub pbs_get_password {
+ my ($scfg, $storeid) = @_;
+
+ my $pwfile = pbs_password_file_name($scfg, $storeid);
+
+ return PVE::Tools::file_read_firstline($pwfile);
+}
+
+sub pbs_encryption_key_file_name {
+ my ($scfg, $storeid) = @_;
+
+ return "${secret_dir}/${storeid}.enc";
+}
+
+sub pbs_set_encryption_key {
+ my ($scfg, $storeid, $key) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+ mkdir $secret_dir;
+
+ PVE::Tools::file_set_contents($encfile, "$key\n", 0600);
+}
+
+sub pbs_delete_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+
+ if (!unlink $encfile) {
+ return if $! == ENOENT;
+ die "failed to delete encryption key! $!\n";
+ }
+}
+
+sub pbs_get_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
+
+ return PVE::Tools::file_get_contents($encfile);
+}
+
+# Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
+sub pbs_open_encryption_key {
+ my ($scfg, $storeid) = @_;
+
+ my $encryption_key_file = pbs_encryption_key_file_name($scfg, $storeid);
+
+ my $keyfd;
+ if (!open($keyfd, '<', $encryption_key_file)) {
+ return undef if $! == ENOENT;
+ die "failed to open encryption key: $encryption_key_file: $!\n";
+ }
+
+ return $keyfd;
+}
+
+my $USE_CRYPT_PARAMS = {
+ backup => 1,
+ restore => 1,
+ 'upload-log' => 1,
+};
+
+my sub do_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+
+ #FIXME: my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
+ my $use_crypto = 0;
+
+ my $client_exe = '/usr/bin/proxmox-backup-client';
+ die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
+ if ! -x $client_exe;
+
+ my $server = $scfg->{server};
+ my $datastore = $scfg->{datastore};
+ my $username = $scfg->{username} // 'root at pam';
+
+ my $userns_cmd = delete $opts{userns_cmd};
+
+ my $cmd = [];
+
+ push @$cmd, @$userns_cmd if defined($userns_cmd);
+
+ push @$cmd, $client_exe, $client_cmd;
+
+ # This must live in the top scope to not get closed before the `run_command`
+ my $keyfd;
+ if ($use_crypto) {
+ if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) {
+ my $flags = fcntl($keyfd, F_GETFD, 0)
+ // die "failed to get file descriptor flags: $!\n";
+ fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
+ or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
+ push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
+ } else {
+ push @$cmd, '--crypt-mode=none';
+ }
+ }
+
+ push @$cmd, @$param if defined($param);
+
+ push @$cmd, "--repository", "$username\@$server:$datastore";
+
+ local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
+
+ local $ENV{PBS_FINGERPRINT} = $scfg->{fingerprint};
+
+ # no ascii-art on task logs
+ local $ENV{PROXMOX_OUTPUT_NO_BORDER} = 1;
+ local $ENV{PROXMOX_OUTPUT_NO_HEADER} = 1;
+
+ if (my $logfunc = $opts{logfunc}) {
+ $logfunc->("run: " . join(' ', @$cmd));
+ }
+
+ run_command($cmd, %opts);
+}
+
+# FIXME: External perl code should NOT have access to this.
+#
+# There should be separate functions to
+# - make backups
+# - restore backups
+# - restore files
+# with a sane API
+sub run_raw_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
+ return do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, %opts);
+}
+
+sub run_client_cmd {
+ my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_;
+
+ my $json_str = '';
+ my $outfunc = sub { $json_str .= "$_[0]\n" };
+
+ $param = [] if !defined($param);
+ $param = [ $param ] if !ref($param);
+
+ $param = [@$param, '--output-format=json'] if !$no_output;
+
+ do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
+ outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
+
+ return undef if $no_output;
+
+ my $res = decode_json($json_str);
+
+ return $res;
+}
+
+my $autogen_encryption_key = sub {
+ my ($scfg, $storeid) = @_;
+ my $encfile = pbs_encryption_key_file_name($storeid);
+ run_command(['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile]);
+};
+
+sub print_snapshot {
+ my ($storeid, $btype, $bid, $btime) = @_;
+
+ my $time_str = strftime("%FT%TZ", gmtime($btime));
+ my $volname = "backup/${btype}/${bid}/${time_str}";
+
+ return "${storeid}:${volname}";
+}
+
+sub status {
+ my ($class, $storeid, $scfg) = @_;
+
+ my $total = 0;
+ my $free = 0;
+ my $used = 0;
+ my $active = 0;
+
+ eval {
+ my $res = run_client_cmd($scfg, $storeid, "status");
+
+ $active = 1;
+ $total = $res->{total};
+ $used = $res->{used};
+ $free = $res->{avail};
+ };
+ if (my $err = $@) {
+ warn $err;
+ }
+
+ return ($total, $free, $used, $active);
+}
+
+1;
--
2.20.1
More information about the pmg-devel
mailing list