* [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups
@ 2026-06-03 18:03 Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 01/15] pbs-client: autogen key: rename old one if existing Stoiko Ivanov
` (14 more replies)
0 siblings, 15 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
The following series adds support for adding an encryption-key to a pbs-remote
used for pmg backups. It aims for feature-parity with the encryption settings
and UX for pbs storages in pve.
The code was mostly copied from pve-storage and pve-manager, with minimal
adaptations.
I did some end to end tests and played around a bit with the UI and CLI:
* adding remotes with autogenerated keys
* deleting/replacing/uploading encryption keys to an existing remote
* adding a remote with provided master-pubkey, creating a backup and
restoring it with proxmox-backup-client following the pbs-docs.
More testing would be very much appreciated.
pve-common:
Stoiko Ivanov (2):
pbs-client: autogen key: rename old one if existing
pbs-client: add support for master public key
src/PVE/PBSClient.pm | 62 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
pmg-api:
Stoiko Ivanov (7):
api: pbs remote: fix delete_password invocation
fix #3226: pbs backup: remote: add encryption key support
pbs: job: add encrypted state to snapshot listing
pbs: job: add verification state to snapshot listing
pmgbackup: add encypted and verification state to output
api: pbs remote create/update: return parts of the configuration
api: pmgbackup: add master-pubkey properties
src/PMG/API2/PBS/Job.pm | 38 ++++++++--
src/PMG/API2/PBS/Remote.pm | 147 +++++++++++++++++++++++++++++++++++--
src/PMG/CLI/pmgbackup.pm | 24 +++++-
src/PMG/PBSConfig.pm | 12 +++
4 files changed, 204 insertions(+), 17 deletions(-)
pmg-gui:
Stoiko Ivanov (5):
pbs: snapshotview: add missing gettext invocations
utils: copy pbs helpers from pve-manager
fix #3326: ui: pbs remote: add encryption tab to edit window
ui: pbs remote: allow to downloading/print new encryption key
ui: pbs snapshotview: add encryption and verification state
js/PBSRemoteEdit.js | 466 ++++++++++++++++++++++++++++++++++++++++++
js/PBSSnapshotView.js | 25 ++-
js/Utils.js | 44 ++++
3 files changed, 529 insertions(+), 6 deletions(-)
pmg-docs:
Stoiko Ivanov (1):
pmgbackup: minimally document support for encrypted backups
pmgbackup.adoc | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
Summary over all repositories:
9 files changed, 810 insertions(+), 24 deletions(-)
--
Generated by murpp 0.12.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH pve-common 01/15] pbs-client: autogen key: rename old one if existing
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 02/15] pbs-client: add support for master public key Stoiko Ivanov
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
while looking into adding encryption support for PMG, I noticed the
discrepancy between PVE::Storage::PBSPlugin and PVE::PBSClient
Semantically this follows:
478609d ("pbs: autogen key: rename old one if existing")
in pve-storage.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PVE/PBSClient.pm | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/PVE/PBSClient.pm b/src/PVE/PBSClient.pm
index 5af0d00..d8dd3f0 100644
--- a/src/PVE/PBSClient.pm
+++ b/src/PVE/PBSClient.pm
@@ -238,6 +238,9 @@ my sub run_client_cmd : prototype($$;$$$$) {
sub autogen_encryption_key {
my ($self) = @_;
my $encfile = $self->encryption_key_file_name();
+ if (-f $encfile) {
+ rename $encfile, "$encfile.old";
+ }
run_command(
['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile],
errmsg => 'failed to create encryption key',
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pve-common 02/15] pbs-client: add support for master public key
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 01/15] pbs-client: autogen key: rename old one if existing Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 03/15] api: pbs remote: fix delete_password invocation Stoiko Ivanov
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
adapted from PVE::Storage::PBSPlugin
originally introduced there with
c56f7a7 ("pbs: allow setting up a master key")
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PVE/PBSClient.pm | 59 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/src/PVE/PBSClient.pm b/src/PVE/PBSClient.pm
index d8dd3f0..34ce843 100644
--- a/src/PVE/PBSClient.pm
+++ b/src/PVE/PBSClient.pm
@@ -132,6 +132,51 @@ my sub open_encryption_key {
return $keyfd;
}
+sub master_pubkey_file_name {
+ my ($self) = @_;
+
+ return "$self->{secret_dir}/$self->{storeid}.master.pem";
+}
+
+sub set_master_pubkey {
+ my ($self, $key) = @_;
+
+ my $master_pubkey_file = $self->master_pubkey_file_name();
+ mkdir($self->{secret_dir});
+
+ PVE::Tools::file_set_contents($master_pubkey_file, "$key\n", 0600);
+}
+
+sub delete_master_pubkey {
+ my ($self) = @_;
+
+ my $master_pubkey_file = $self->master_pubkey_file_name();
+
+ if (!unlink($master_pubkey_file)) {
+ return if $! == ENOENT;
+ die "failed to delete the master public key! $!\n";
+ }
+}
+
+# Returns a file handle if there is a master key, or `undef` if there is not. Dies on error.
+sub open_master_pubkey {
+ my ($self) = @_;
+
+ my $master_pubkey_file = $self->master_pubkey_file_name();
+
+ my $keyfd;
+ if (!open($keyfd, '<', $master_pubkey_file)) {
+ if ($! == ENOENT) {
+ die "master public key configured but no key file found!\n"
+ if $self->{'master-pubkey'};
+ return undef;
+ }
+ die "failed to open master public key: $master_pubkey_file: $!\n";
+ }
+
+ return $keyfd;
+}
+
my $USE_CRYPT_PARAMS = {
'proxmox-backup-client' => {
backup => 1,
@@ -144,11 +189,16 @@ my $USE_CRYPT_PARAMS = {
},
};
+my $USE_MASTER_KEY = {
+ backup => 1,
+};
+
my sub do_raw_client_cmd {
my ($self, $client_cmd, $param, %opts) = @_;
my $client_bin = delete($opts{binary}) || 'proxmox-backup-client';
my $use_crypto = $USE_CRYPT_PARAMS->{$client_bin}->{$client_cmd} // 0;
+ my $use_master = $USE_MASTER_KEY->{$client_cmd};
my $client_exe = "/usr/bin/$client_bin";
die "executable not found '$client_exe'! $client_bin not installed?\n" if !-x $client_exe;
@@ -165,7 +215,7 @@ my sub do_raw_client_cmd {
push(@$cmd, $client_exe, $client_cmd);
# This must live in the top scope to not get closed before the `run_command`
- my $keyfd;
+ my ($keyfd, $master_fd);
if ($use_crypto) {
if (defined($keyfd = open_encryption_key($self))) {
my $flags = fcntl($keyfd, F_GETFD, 0)
@@ -173,6 +223,13 @@ my sub do_raw_client_cmd {
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));
+ if ($use_master && defined($master_fd = $self->open_master_pubkey())) {
+ my $flags = fcntl($master_fd, F_GETFD, 0)
+ // die "failed to get file descriptor flags: $!\n";
+ fcntl($master_fd, F_SETFD, $flags & ~FD_CLOEXEC)
+ or die "failed to remove FD_CLOEXEC from master public key file descriptor\n";
+ push @$cmd, '--master-pubkey-fd=' . fileno($master_fd);
+ }
} else {
push(@$cmd, '--crypt-mode=none');
}
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 03/15] api: pbs remote: fix delete_password invocation
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 01/15] pbs-client: autogen key: rename old one if existing Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 02/15] pbs-client: add support for master public key Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 04/15] fix #3226: pbs backup: remote: add encryption key support Stoiko Ivanov
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
PVE::PBSClient already has the remote-id - so this parameter is not
needed/confusing.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Remote.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/PMG/API2/PBS/Remote.pm b/src/PMG/API2/PBS/Remote.pm
index 561b4052..e5d63e68 100644
--- a/src/PMG/API2/PBS/Remote.pm
+++ b/src/PMG/API2/PBS/Remote.pm
@@ -216,7 +216,7 @@ __PACKAGE__->register_method({
die "PBS remote '$remote' does not exist\n" if !$ids->{$remote};
my $pbs = PVE::PBSClient->new($ids->{$remote}, $remote, $conf->{secret_dir});
- $pbs->delete_password($remote);
+ $pbs->delete_password();
delete $ids->{$remote};
$conf->write();
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 04/15] fix #3226: pbs backup: remote: add encryption key support
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (2 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 03/15] api: pbs remote: fix delete_password invocation Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 05/15] pbs: job: add encrypted state to snapshot listing Stoiko Ivanov
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
semantically this is copied from pve-storage while using
PVE::PBSClient.
tested with `pmgbackup proxmox-backup remote`
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Remote.pm | 46 ++++++++++++++++++++++++++++++++++++++
src/PMG/PBSConfig.pm | 6 +++++
2 files changed, 52 insertions(+)
diff --git a/src/PMG/API2/PBS/Remote.pm b/src/PMG/API2/PBS/Remote.pm
index e5d63e68..881ab127 100644
--- a/src/PMG/API2/PBS/Remote.pm
+++ b/src/PMG/API2/PBS/Remote.pm
@@ -3,6 +3,8 @@ package PMG::API2::PBS::Remote;
use strict;
use warnings;
+use JSON;
+
use PVE::SafeSyslog;
use PVE::Tools qw(extract_param);
use PVE::JSONSchema qw(get_standard_option);
@@ -84,6 +86,26 @@ __PACKAGE__->register_method({
my $pbs = PVE::PBSClient->new($remotecfg, $remote, $conf->{secret_dir});
$pbs->set_password($password) if defined($password);
+ my $encryption_key = extract_param($remotecfg, 'encryption-key');
+
+ if (defined($encryption_key)) {
+ my $decoded_key;
+ if ($encryption_key eq 'autogen') {
+ $encryption_key = $pbs->autogen_encryption_key();
+ $decoded_key = decode_json($encryption_key);
+ } else {
+ $decoded_key = eval { decode_json($encryption_key) };
+ if ($@ || !exists($decoded_key->{data})) {
+ die
+ "Value does not seems like a valid, JSON formatted encryption key!\n";
+ }
+ $pbs->set_encryption_key($encryption_key);
+ }
+ $remotecfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
+ } else {
+ $pbs->delete_encryption_key();
+ }
+
$ids->{$remote} = $remotecfg;
$conf->write();
};
@@ -164,6 +186,9 @@ __PACKAGE__->register_method({
if ($opt eq 'password') {
$pbs->delete_password();
}
+ if ($opt eq 'encryption-key') {
+ $pbs->delete_encryption_key();
+ }
delete $ids->{$remote}->{$opt};
}
@@ -171,6 +196,26 @@ __PACKAGE__->register_method({
$pbs->set_password($password);
}
+ if (exists($param->{'encryption-key'})) {
+ if (defined(my $encryption_key = extract_param($param, 'encryption-key'))) {
+ my $decoded_key;
+ if ($encryption_key eq 'autogen') {
+ $encryption_key = $pbs->autogen_encryption_key();
+ $decoded_key = decode_json($encryption_key);
+ } else {
+ $decoded_key = eval { decode_json($encryption_key) };
+ if ($@ || !exists($decoded_key->{data})) {
+ die
+ "Value does not seems like a valid, JSON formatted encryption key!\n";
+ }
+ $pbs->set_encryption_key($encryption_key);
+ }
+ $param->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
+ } else {
+ $pbs->delete_encryption_key();
+ }
+ }
+
my $remoteconfig = PMG::PBSConfig->check_config($remote, $param, 0, 1);
foreach my $p (keys %$remoteconfig) {
@@ -217,6 +262,7 @@ __PACKAGE__->register_method({
my $pbs = PVE::PBSClient->new($ids->{$remote}, $remote, $conf->{secret_dir});
$pbs->delete_password();
+ $pbs->delete_encryption_key();
delete $ids->{$remote};
$conf->write();
diff --git a/src/PMG/PBSConfig.pm b/src/PMG/PBSConfig.pm
index 8498893c..4ceb81a3 100644
--- a/src/PMG/PBSConfig.pm
+++ b/src/PMG/PBSConfig.pm
@@ -125,6 +125,11 @@ sub properties {
type => 'boolean',
optional => 1,
},
+ 'encryption-key' => {
+ description =>
+ "Encryption key. Use 'autogen' to generate one automatically without passphrase.",
+ type => 'string',
+ },
%prune_properties,
};
}
@@ -147,6 +152,7 @@ sub options {
'keep-weekly' => { optional => 1 },
'keep-monthly' => { optional => 1 },
'keep-yearly' => { optional => 1 },
+ 'encryption-key' => { optional => 1 },
};
}
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 05/15] pbs: job: add encrypted state to snapshot listing
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (3 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 04/15] fix #3226: pbs backup: remote: add encryption key support Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 06/15] pbs: job: add verification " Stoiko Ivanov
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
follows pve-storage commit:
878fe01 ("api: content: pass encrypted status for PBS backups")
the fallback to the 'crypt-mode' of the individual files was not
added, as this was a backward compat fix before
https://bugzilla.proxmox.com/show_bug.cgi?id=3139
was fixed in 2022/PBS 1.0.3.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Job.pm | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/src/PMG/API2/PBS/Job.pm b/src/PMG/API2/PBS/Job.pm
index a2233456..a4410d59 100644
--- a/src/PMG/API2/PBS/Job.pm
+++ b/src/PMG/API2/PBS/Job.pm
@@ -116,14 +116,16 @@ my sub get_snapshots {
next if (scalar(@pxar) != 1);
my $time_rfc3339 = strftime("%FT%TZ", gmtime($time));
+ my $res_item = {
+ 'backup-id' => $id,
+ 'backup-time' => $time_rfc3339,
+ ctime => $time,
+ size => $item->{size} // 1,
+ };
- push @$res,
- {
- 'backup-id' => $id,
- 'backup-time' => $time_rfc3339,
- ctime => $time,
- size => $item->{size} // 1,
- };
+ $res_item->{encrypted} = $item->{fingerprint} if defined($item->{fingerprint});
+
+ push @$res, $res_item;
}
return $res;
}
@@ -156,6 +158,12 @@ __PACKAGE__->register_method({
'backup-id' => { type => 'string' },
ctime => { type => 'string' },
size => { type => 'integer' },
+ encrypted => {
+ description =>
+ "If the backup is encrypted the value is the encryption-key fingerprint",
+ type => 'string',
+ optional => 1,
+ },
},
},
links => [{ rel => 'child', href => "{backup-id}" }],
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 06/15] pbs: job: add verification state to snapshot listing
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (4 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 05/15] pbs: job: add encrypted state to snapshot listing Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 07/15] pmgbackup: add encypted and verification state to output Stoiko Ivanov
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
follows pve-storage commit
9778e5c ("api: content listing: add comment and verification fields")
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Job.pm | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/PMG/API2/PBS/Job.pm b/src/PMG/API2/PBS/Job.pm
index a4410d59..636d3589 100644
--- a/src/PMG/API2/PBS/Job.pm
+++ b/src/PMG/API2/PBS/Job.pm
@@ -124,6 +124,7 @@ my sub get_snapshots {
};
$res_item->{encrypted} = $item->{fingerprint} if defined($item->{fingerprint});
+ $res_item->{verification} = $item->{verification} if defined($item->{verification});
push @$res, $res_item;
}
@@ -164,6 +165,21 @@ __PACKAGE__->register_method({
type => 'string',
optional => 1,
},
+ verification => {
+ description => "Backup verification result",
+ type => 'object',
+ properties => {
+ state => {
+ description => "Backup verification state.",
+ type => 'string',
+ },
+ upid => {
+ description => "Backup verification UPID.",
+ type => 'string',
+ },
+ },
+ optional => 1,
+ },
},
},
links => [{ rel => 'child', href => "{backup-id}" }],
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 07/15] pmgbackup: add encypted and verification state to output
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (5 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 06/15] pbs: job: add verification " Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 08/15] api: pbs remote create/update: return parts of the configuration Stoiko Ivanov
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
print only the state of the verification result, and yes/no for
encryption instead of the fingerprint.
the rework of the output sub is inspired by
pve-ha-manager/src/PVE/CLI/ha_manager.pm
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/CLI/pmgbackup.pm | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/PMG/CLI/pmgbackup.pm b/src/PMG/CLI/pmgbackup.pm
index 11dd5672..9ef0c3c7 100644
--- a/src/PMG/CLI/pmgbackup.pm
+++ b/src/PMG/CLI/pmgbackup.pm
@@ -114,8 +114,15 @@ our $cmddef = {
{ node => $nodename },
sub {
my ($data, $schema, $options) = @_;
+ my $props_to_print =
+ ['backup-id', 'backup-time', 'size', 'encrypted', 'verify-state'];
+ for my $snapshot (@$data) {
+ $snapshot->{encrypted} = $snapshot->{encrypted} ? "yes" : "no";
+ $snapshot->{'verify-state'} = $snapshot->{verification}->{state};
+ }
+
PVE::CLIFormatter::print_api_result(
- $data, $schema, ['backup-id', 'backup-time', 'size'], $options,
+ $data, $schema, $props_to_print, $options,
);
},
$PVE::RESTHandler::standard_output_options,
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 08/15] api: pbs remote create/update: return parts of the configuration
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (6 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 07/15] pmgbackup: add encypted and verification state to output Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 09/15] api: pmgbackup: add master-pubkey properties Stoiko Ivanov
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
This follows pve-storage commit:
cd69ced ("api: storage create/update: return parts of the configuration")
returning the encryption-key upon creation, to offer it for
download/copying to the user in the GUI.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Remote.pm | 75 ++++++++++++++++++++++++++++++++++----
1 file changed, 67 insertions(+), 8 deletions(-)
diff --git a/src/PMG/API2/PBS/Remote.pm b/src/PMG/API2/PBS/Remote.pm
index 881ab127..b5b9c3ad 100644
--- a/src/PMG/API2/PBS/Remote.pm
+++ b/src/PMG/API2/PBS/Remote.pm
@@ -67,16 +67,39 @@ __PACKAGE__->register_method({
proxyto => 'master',
protected => 1,
parameters => PMG::PBSConfig->createSchema(1),
- returns => { type => 'null' },
+ returns => {
+ type => 'object',
+ properties => {
+ remote => {
+ description => "The ID of the created PBS remote.",
+ type => 'string',
+ },
+ config => {
+ description => "Partial, possibly server generated, configuration properties.",
+ type => 'object',
+ optional => 1,
+ additionalProperties => 1,
+ properties => {
+ 'encryption-key' => {
+ description => "The, possibly auto-generated, encryption-key.",
+ optional => 1,
+ type => 'string',
+ },
+ },
+ },
+ },
+ },
code => sub {
my ($param) = @_;
+ my $remote;
+ my $encryption_key;
my $code = sub {
my $conf = PMG::PBSConfig->new();
$conf->{ids} //= {};
my $ids = $conf->{ids};
- my $remote = extract_param($param, 'remote');
+ $remote = extract_param($param, 'remote');
die "PBS remote '$remote' already exists\n" if $ids->{$remote};
my $remotecfg = PMG::PBSConfig->check_config($remote, $param, 1);
@@ -86,7 +109,7 @@ __PACKAGE__->register_method({
my $pbs = PVE::PBSClient->new($remotecfg, $remote, $conf->{secret_dir});
$pbs->set_password($password) if defined($password);
- my $encryption_key = extract_param($remotecfg, 'encryption-key');
+ $encryption_key = extract_param($remotecfg, 'encryption-key');
if (defined($encryption_key)) {
my $decoded_key;
@@ -111,8 +134,14 @@ __PACKAGE__->register_method({
};
PMG::PBSConfig::lock_config($code, "add PBS remote failed");
+ my $res = {
+ remote => $remote,
+ config => {
+ 'encryption-key' => $encryption_key,
+ },
+ };
- return undef;
+ return $res;
},
});
@@ -162,10 +191,33 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => 'master',
parameters => PMG::PBSConfig->updateSchema(),
- returns => { type => 'null' },
+ returns => {
+ type => 'object',
+ properties => {
+ remote => {
+ description => "The ID of the created PBS remote.",
+ type => 'string',
+ },
+ config => {
+ description => "Partial, possibly server generated, configuration properties.",
+ type => 'object',
+ optional => 1,
+ additionalProperties => 1,
+ properties => {
+ 'encryption-key' => {
+ description => "The, possibly auto-generated, encryption-key.",
+ optional => 1,
+ type => 'string',
+ },
+ },
+ },
+ },
+ },
code => sub {
my ($param) = @_;
+ my $remote;
+ my $encryption_key;
my $code = sub {
my $conf = PMG::PBSConfig->new();
@@ -174,7 +226,7 @@ __PACKAGE__->register_method({
my $digest = extract_param($param, 'digest');
PVE::SectionConfig::assert_if_modified($conf, $digest);
- my $remote = extract_param($param, 'remote');
+ $remote = extract_param($param, 'remote');
die "PBS remote '$remote' does not exist\n" if !$ids->{$remote};
@@ -197,7 +249,7 @@ __PACKAGE__->register_method({
}
if (exists($param->{'encryption-key'})) {
- if (defined(my $encryption_key = extract_param($param, 'encryption-key'))) {
+ if (defined($encryption_key = extract_param($param, 'encryption-key'))) {
my $decoded_key;
if ($encryption_key eq 'autogen') {
$encryption_key = $pbs->autogen_encryption_key();
@@ -227,7 +279,14 @@ __PACKAGE__->register_method({
PMG::PBSConfig::lock_config($code, "update PBS remote failed");
- return undef;
+ my $res = {
+ remote => $remote,
+ config => {
+ 'encryption-key' => $encryption_key,
+ },
+ };
+
+ return $res;
},
});
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-api 09/15] api: pmgbackup: add master-pubkey properties
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (7 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 08/15] api: pbs remote create/update: return parts of the configuration Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 10/15] pbs: snapshotview: add missing gettext invocations Stoiko Ivanov
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
adapted from pve-storage commit
c56f7a7 ("pbs: allow setting up a master key")
the actual invocation of proxmox-backup-client with the master-key
needs a versioned dependency bump on pve-common.
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
src/PMG/API2/PBS/Remote.pm | 28 ++++++++++++++++++++++++++++
src/PMG/CLI/pmgbackup.pm | 15 +++++++++++++--
src/PMG/PBSConfig.pm | 6 ++++++
3 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/src/PMG/API2/PBS/Remote.pm b/src/PMG/API2/PBS/Remote.pm
index b5b9c3ad..397d802b 100644
--- a/src/PMG/API2/PBS/Remote.pm
+++ b/src/PMG/API2/PBS/Remote.pm
@@ -4,6 +4,7 @@ use strict;
use warnings;
use JSON;
+use MIME::Base64 qw(decode_base64);
use PVE::SafeSyslog;
use PVE::Tools qw(extract_param);
@@ -102,6 +103,7 @@ __PACKAGE__->register_method({
$remote = extract_param($param, 'remote');
die "PBS remote '$remote' already exists\n" if $ids->{$remote};
+ my $master_key = extract_param($param, 'master-pubkey');
my $remotecfg = PMG::PBSConfig->check_config($remote, $param, 1);
my $password = extract_param($remotecfg, 'password');
@@ -129,6 +131,17 @@ __PACKAGE__->register_method({
$pbs->delete_encryption_key();
}
+ if (defined($master_key)) {
+ die "'master-pubkey' can only be used together with 'encryption-key'\n"
+ if !defined($remotecfg->{'encryption-key'});
+
+ my $decoded = decode_base64($master_key);
+ $pbs->set_master_pubkey($decoded);
+ $remotecfg->{'master-pubkey'} = 1;
+ } else {
+ $pbs->delete_master_pubkey();
+ }
+
$ids->{$remote} = $remotecfg;
$conf->write();
};
@@ -241,6 +254,9 @@ __PACKAGE__->register_method({
if ($opt eq 'encryption-key') {
$pbs->delete_encryption_key();
}
+ if ($opt eq 'master-pubkey') {
+ $pbs->delete_master_pubkey();
+ }
delete $ids->{$remote}->{$opt};
}
@@ -268,6 +284,17 @@ __PACKAGE__->register_method({
}
}
+ if (exists($param->{'master-pubkey'})) {
+ if (defined(my $master_key = extract_param($param, 'master-pubkey'))) {
+ my $decoded = decode_base64($master_key);
+
+ $pbs->set_master_pubkey($decoded);
+ $param->{'master-pubkey'} = 1;
+ } else {
+ $pbs->delete_master_pubkey();
+ }
+ }
+
my $remoteconfig = PMG::PBSConfig->check_config($remote, $param, 0, 1);
foreach my $p (keys %$remoteconfig) {
@@ -322,6 +349,7 @@ __PACKAGE__->register_method({
my $pbs = PVE::PBSClient->new($ids->{$remote}, $remote, $conf->{secret_dir});
$pbs->delete_password();
$pbs->delete_encryption_key();
+ $pbs->delete_master_pubkey();
delete $ids->{$remote};
$conf->write();
diff --git a/src/PMG/CLI/pmgbackup.pm b/src/PMG/CLI/pmgbackup.pm
index 9ef0c3c7..43428ef2 100644
--- a/src/PMG/CLI/pmgbackup.pm
+++ b/src/PMG/CLI/pmgbackup.pm
@@ -3,6 +3,8 @@ package PMG::CLI::pmgbackup;
use strict;
use warnings;
+use MIME::Base64 qw(encode_base64);
+
use PVE::Tools;
use PVE::SafeSyslog;
use PVE::INotify;
@@ -43,9 +45,18 @@ sub param_mapping {
},
};
+ my $master_key_map = {
+ name => 'master-pubkey',
+ desc => 'a file containing a PEM-formatted master public key',
+ func => sub {
+ my ($value) = @_;
+ return encode_base64(PVE::Tools::file_get_contents($value), '');
+ },
+ };
+
my $mapping = {
- 'create' => [$password_map, $enc_key_map],
- 'update_config' => [$password_map, $enc_key_map],
+ 'create' => [$password_map, $enc_key_map, $master_key_map],
+ 'update_config' => [$password_map, $enc_key_map, $master_key_map],
};
return $mapping->{$name};
}
diff --git a/src/PMG/PBSConfig.pm b/src/PMG/PBSConfig.pm
index 4ceb81a3..ec4b5405 100644
--- a/src/PMG/PBSConfig.pm
+++ b/src/PMG/PBSConfig.pm
@@ -130,6 +130,11 @@ sub properties {
"Encryption key. Use 'autogen' to generate one automatically without passphrase.",
type => 'string',
},
+ 'master-pubkey' => {
+ description =>
+ "Base64-encoded, PEM-formatted public RSA key. Used to encrypt a copy of the encryption-key which will be added to each encrypted backup.",
+ type => 'string',
+ },
%prune_properties,
};
}
@@ -153,6 +158,7 @@ sub options {
'keep-monthly' => { optional => 1 },
'keep-yearly' => { optional => 1 },
'encryption-key' => { optional => 1 },
+ 'master-pubkey' => { optional => 1 },
};
}
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-gui 10/15] pbs: snapshotview: add missing gettext invocations
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (8 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-api 09/15] api: pmgbackup: add master-pubkey properties Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 11/15] utils: copy pbs helpers from pve-manager Stoiko Ivanov
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/PBSSnapshotView.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/js/PBSSnapshotView.js b/js/PBSSnapshotView.js
index f3fe475..82f0f96 100644
--- a/js/PBSSnapshotView.js
+++ b/js/PBSSnapshotView.js
@@ -252,17 +252,17 @@ Ext.define('PMG.PBSConfig', {
},
columns: [
{
- text: 'Group ID',
+ text: gettext('Group ID'),
dataIndex: 'backup-id',
flex: 1,
},
{
- text: 'Time',
+ text: gettext('Time'),
dataIndex: 'backup-time',
width: 180,
},
{
- text: 'Size',
+ text: gettext('Size'),
dataIndex: 'size',
renderer: Proxmox.Utils.render_size,
flex: 1,
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-gui 11/15] utils: copy pbs helpers from pve-manager
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (9 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-gui 10/15] pbs: snapshotview: add missing gettext invocations Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 12/15] fix #3326: ui: pbs remote: add encryption tab to edit window Stoiko Ivanov
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
as we'll reuse the widgets from pve-manager for adding pbs encryption
support to PMG, copy the helpers from pve-manager.
taken from pve-manager commits:
fdde857a ("fix #4393: ui: storage backup view: make pbs-specific columns sortable")
957a53bd ("ui: add comment/verification columns to backup/content grid")
3003a59d ("ui: guest backup view: add encrypted column for PBS storages")
14ba33fb ("ui: storage: pbs: improve encryption key handling")
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/Utils.js | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/js/Utils.js b/js/Utils.js
index 3332f9b..0c3032f 100644
--- a/js/Utils.js
+++ b/js/Utils.js
@@ -910,6 +910,50 @@ Ext.define('PMG.Utils', {
saupdate: ['', gettext('SpamAssassin update')],
});
},
+
+ render_pbs_fingerprint: (fp) => fp.substring(0, 23),
+
+ render_backup_encryption: function (v, meta, record) {
+ if (!v) {
+ return gettext('No');
+ }
+ let tip = '';
+ if (v.match(/^[a-fA-F0-9]{2}:/)) {
+ // fingerprint
+ tip = `Key fingerprint ${PMG.Utils.render_pbs_fingerprint(v)}`;
+ }
+ let icon = `<i class="fa fa-fw fa-lock good"></i>`;
+ return `<span data-qtip="${tip}">${icon} ${gettext('Encrypted')}</span>`;
+ },
+
+ render_backup_verification: function (v, meta, record) {
+ let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
+ if (v === undefined || v === null) {
+ return i('question-circle-o warning', gettext('None'));
+ }
+ let tip = '';
+ let txt = gettext('Failed');
+ let iconCls = 'times critical';
+ if (v.state === 'ok') {
+ txt = gettext('OK');
+ iconCls = 'check good';
+ let now = Date.now() / 1000;
+ let task = Proxmox.Utils.parse_task_upid(v.upid);
+ let verify_time = Proxmox.Utils.render_timestamp(task.starttime);
+ tip = `Last verify task started on ${verify_time}`;
+ if (now - v.starttime > 30 * 24 * 60 * 60) {
+ tip = `Last verify task over 30 days ago: ${verify_time}`;
+ iconCls = 'check warning';
+ }
+ }
+ return `<span data-qtip="${tip}"> ${i(iconCls, txt)} </span>`;
+ },
+ verificationStateOrder: {
+ failed: 0,
+ none: 1,
+ ok: 2,
+ __default__: 3,
+ },
});
Ext.define('PMG.Async', {
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-gui 12/15] fix #3326: ui: pbs remote: add encryption tab to edit window
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (10 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-gui 11/15] utils: copy pbs helpers from pve-manager Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 13/15] ui: pbs remote: allow to downloading/print new encryption key Stoiko Ivanov
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
code is taken from pve-manager/www/manager6/storage/PBSEdit.js
and minimally adapted (onlineHelp property, renaming of utils class).
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/PBSRemoteEdit.js | 255 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 255 insertions(+)
diff --git a/js/PBSRemoteEdit.js b/js/PBSRemoteEdit.js
index 8ccc39c..aa51184 100644
--- a/js/PBSRemoteEdit.js
+++ b/js/PBSRemoteEdit.js
@@ -1,3 +1,253 @@
+Ext.define('PMG.panel.PBSEncryptionKeyTab', {
+ extend: 'Proxmox.panel.InputPanel',
+ xtype: 'pmgPBSEncryptionKeyTab',
+ mixins: ['Proxmox.Mixin.CBind'],
+
+ onlineHelp: 'pmgbackup_pbs_remotes',
+
+ onGetValues: function (form) {
+ let values = {};
+ if (form.cryptMode === 'upload') {
+ values['encryption-key'] = form['crypt-key-upload'];
+ } else if (form.cryptMode === 'autogenerate') {
+ values['encryption-key'] = 'autogen';
+ } else if (form.cryptMode === 'none') {
+ if (!this.isCreate) {
+ values.delete = ['encryption-key'];
+ }
+ }
+ return values;
+ },
+
+ setValues: function (values) {
+ let me = this;
+ let vm = me.getViewModel();
+
+ let cryptKeyInfo = values['encryption-key'];
+ if (cryptKeyInfo) {
+ let icon = '<span class="fa fa-lock good"></span> ';
+ if (cryptKeyInfo.match(/^[a-fA-F0-9]{2}:/)) {
+ // new style fingerprint
+ let shortKeyFP = PMG.Utils.render_pbs_fingerprint(cryptKeyInfo);
+ values['crypt-key-fp'] =
+ icon + `${gettext('Active')} - ${gettext('Fingerprint')} ${shortKeyFP}`;
+ } else {
+ // old key without FP
+ values['crypt-key-fp'] = icon + gettext('Active');
+ }
+ values.cryptMode = 'keep';
+ values['crypt-allow-edit'] = false;
+ } else {
+ values['crypt-key-fp'] = gettext('None');
+ let cryptModeNone = me.down('radiofield[inputValue=none]');
+ cryptModeNone.setBoxLabel(gettext('Do not encrypt backups'));
+ values.cryptMode = 'none';
+ values['crypt-allow-edit'] = true;
+ }
+ vm.set('keepCryptVisible', !!cryptKeyInfo);
+ vm.set('allowEdit', !cryptKeyInfo);
+
+ me.callParent([values]);
+ },
+
+ viewModel: {
+ data: {
+ allowEdit: true,
+ keepCryptVisible: false,
+ },
+ formulas: {
+ showDangerousHint: (get) => {
+ let allowEdit = get('allowEdit');
+ return get('keepCryptVisible') && allowEdit;
+ },
+ },
+ },
+
+ items: [
+ {
+ xtype: 'displayfield',
+ name: 'crypt-key-fp',
+ fieldLabel: gettext('Encryption Key'),
+ padding: '2 0',
+ },
+ {
+ xtype: 'checkbox',
+ name: 'crypt-allow-edit',
+ boxLabel: gettext('Edit existing encryption key (dangerous!)'),
+ hidden: true,
+ submitValue: false,
+ isDirty: () => false,
+ bind: {
+ hidden: '{!keepCryptVisible}',
+ value: '{allowEdit}',
+ },
+ },
+ {
+ xtype: 'radiofield',
+ name: 'cryptMode',
+ inputValue: 'keep',
+ boxLabel: gettext('Keep encryption key'),
+ padding: '0 0 0 25',
+ cbind: {
+ hidden: '{isCreate}',
+ },
+ bind: {
+ hidden: '{!keepCryptVisible}',
+ disabled: '{!allowEdit}',
+ },
+ },
+ {
+ xtype: 'radiofield',
+ name: 'cryptMode',
+ inputValue: 'none',
+ checked: true,
+ padding: '0 0 0 25',
+ cbind: {
+ disabled: '{!isCreate}',
+ checked: '{isCreate}',
+ boxLabel: (get) =>
+ get('isCreate')
+ ? gettext('Do not encrypt backups')
+ : gettext('Delete existing encryption key'),
+ },
+ bind: {
+ disabled: '{!allowEdit}',
+ },
+ },
+ {
+ xtype: 'radiofield',
+ name: 'cryptMode',
+ inputValue: 'autogenerate',
+ boxLabel: gettext('Auto-generate a client encryption key'),
+ padding: '0 0 0 25',
+ cbind: {
+ disabled: '{!isCreate}',
+ },
+ bind: {
+ disabled: '{!allowEdit}',
+ },
+ },
+ {
+ xtype: 'radiofield',
+ name: 'cryptMode',
+ inputValue: 'upload',
+ boxLabel: gettext('Upload an existing client encryption key'),
+ padding: '0 0 0 25',
+ cbind: {
+ disabled: '{!isCreate}',
+ },
+ bind: {
+ disabled: '{!allowEdit}',
+ },
+ listeners: {
+ change: function (f, value) {
+ let panel = this.up('inputpanel');
+ if (!panel.rendered) {
+ return;
+ }
+ let uploadKeyField = panel.down('field[name=crypt-key-upload]');
+ uploadKeyField.setDisabled(!value);
+ uploadKeyField.setHidden(!value);
+
+ let uploadKeyButton = panel.down('filebutton[name=crypt-upload-button]');
+ uploadKeyButton.setDisabled(!value);
+ uploadKeyButton.setHidden(!value);
+
+ if (value) {
+ uploadKeyField.validate();
+ } else {
+ uploadKeyField.reset();
+ }
+ },
+ },
+ },
+ {
+ xtype: 'fieldcontainer',
+ layout: 'hbox',
+ items: [
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'crypt-key-upload',
+ fieldLabel: gettext('Key'),
+ value: '',
+ disabled: true,
+ hidden: true,
+ allowBlank: false,
+ labelAlign: 'right',
+ flex: 1,
+ emptyText: gettext('You can drag-and-drop a key file here.'),
+ validator: function (value) {
+ if (value.length) {
+ let key;
+ try {
+ key = JSON.parse(value);
+ } catch (e) {
+ return 'Failed to parse key - ' + e;
+ }
+ if (key.data === undefined) {
+ return 'Does not seems like a valid Proxmox Backup key!';
+ }
+ }
+ return true;
+ },
+ afterRender: function () {
+ if (!window.FileReader) {
+ // No FileReader support in this browser
+ return;
+ }
+ let cancel = function (ev) {
+ ev = ev.event;
+ if (ev.preventDefault) {
+ ev.preventDefault();
+ }
+ };
+ this.inputEl.on('dragover', cancel);
+ this.inputEl.on('dragenter', cancel);
+ this.inputEl.on('drop', (ev) => {
+ cancel(ev);
+ let files = ev.event.dataTransfer.files;
+ Proxmox.Utils.loadTextFromFile(files[0], (v) => this.setValue(v));
+ });
+ },
+ },
+ {
+ xtype: 'filebutton',
+ name: 'crypt-upload-button',
+ iconCls: 'fa fa-fw fa-folder-open-o x-btn-icon-el-default-toolbar-small',
+ cls: 'x-btn-default-toolbar-small proxmox-inline-button',
+ margin: '0 0 0 4',
+ disabled: true,
+ hidden: true,
+ listeners: {
+ change: function (btn, e, value) {
+ let ev = e.event;
+ let field = btn.up().down('proxmoxtextfield[name=crypt-key-upload]');
+ Proxmox.Utils.loadTextFromFile(ev.target.files[0], (v) =>
+ field.setValue(v),
+ );
+ btn.reset();
+ },
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'component',
+ border: false,
+ padding: '5 2',
+ userCls: 'pmx-hint',
+ html: // `<b style="color:red;font-weight:600;">${ngettext('Warning', 'Warnings', 1)}</b>: ` +
+ `<span class="fa fa-exclamation-triangle" style="color:red;font-size:14px;"></span> ` +
+ gettext(
+ 'Deleting or replacing the encryption key will break restoring backups created with it!',
+ ),
+ hidden: true,
+ bind: {
+ hidden: '{!showDangerousHint}',
+ },
+ },
+ ],
+});
Ext.define('PMG.PBSInputPanel', {
extend: 'Ext.tab.Panel',
xtype: 'pmgPBSInputPanel',
@@ -173,6 +423,11 @@ Ext.define('PMG.PBSInputPanel', {
},
],
},
+ {
+ xtype: 'pmgPBSEncryptionKeyTab',
+ title: gettext('Encryption'),
+ isCreate: this.isCreate,
+ },
],
});
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-gui 13/15] ui: pbs remote: allow to downloading/print new encryption key
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (11 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-gui 12/15] fix #3326: ui: pbs remote: add encryption tab to edit window Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 14/15] ui: pbs snapshotview: add encryption and verification state Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-docs 15/15] pmgbackup: minimally document support for encrypted backups Stoiko Ivanov
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
taken from pve-manager/www/manager6/storage/PBSEdit.js
introduced there in commit
d1a7c6ee ("ui: storage/PBS: allow to download/print new encryption key")
minimally adapted (rename storage-id/sid to remote-id/rid,
renaming of utils class).
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/PBSRemoteEdit.js | 211 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 211 insertions(+)
diff --git a/js/PBSRemoteEdit.js b/js/PBSRemoteEdit.js
index aa51184..dfcf9a8 100644
--- a/js/PBSRemoteEdit.js
+++ b/js/PBSRemoteEdit.js
@@ -1,3 +1,201 @@
+Ext.define('PMG.PBSKeyShow', {
+ extend: 'Ext.window.Window',
+ xtype: 'pmgPBSKeyShow',
+ mixins: ['Proxmox.Mixin.CBind'],
+
+ width: 600,
+ modal: true,
+ resizable: false,
+ title: gettext('Important: Save your Encryption Key'),
+
+ // avoid that esc closes this by mistake, force user to more manual action
+ onEsc: Ext.emptyFn,
+ closable: false,
+
+ items: [
+ {
+ xtype: 'form',
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ bodyPadding: 10,
+ border: false,
+ defaults: {
+ anchor: '100%',
+ border: false,
+ padding: '10 0 0 0',
+ },
+ items: [
+ {
+ xtype: 'textfield',
+ fieldLabel: gettext('Key'),
+ labelWidth: 80,
+ inputId: 'encryption-key-value',
+ cbind: {
+ value: '{key}',
+ },
+ editable: false,
+ },
+ {
+ xtype: 'component',
+ html:
+ gettext(
+ 'Keep your encryption key safe, but easily accessible for disaster recovery.',
+ ) +
+ '<br>' +
+ gettext('We recommend the following safe-keeping strategy:'),
+ },
+ {
+ xtyp: 'container',
+ layout: 'hbox',
+ items: [
+ {
+ xtype: 'component',
+ html: '1. ' + gettext('Save the key in your password manager.'),
+ flex: 1,
+ },
+ {
+ xtype: 'button',
+ text: gettext('Copy Key'),
+ iconCls: 'fa fa-clipboard x-btn-icon-el-default-toolbar-small',
+ cls: 'x-btn-default-toolbar-small proxmox-inline-button',
+ width: 110,
+ handler: function (b) {
+ document.getElementById('encryption-key-value').select();
+ document.execCommand('copy');
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ items: [
+ {
+ xtype: 'component',
+ html:
+ '2. ' +
+ gettext(
+ 'Download the key to a USB (pen) drive, placed in secure vault.',
+ ),
+ flex: 1,
+ },
+ {
+ xtype: 'button',
+ text: gettext('Download'),
+ iconCls: 'fa fa-download x-btn-icon-el-default-toolbar-small',
+ cls: 'x-btn-default-toolbar-small proxmox-inline-button',
+ width: 110,
+ handler: function (b) {
+ let win = this.up('window');
+
+ let pmgID = Proxmox.NodeName || window.location.hostname;
+ let name = `pmg-${pmgID}-remote-${win.rid}.enc`;
+
+ let hiddenElement = document.createElement('a');
+ hiddenElement.href = 'data:attachment/text,' + encodeURI(win.key);
+ hiddenElement.target = '_blank';
+ hiddenElement.download = name;
+ hiddenElement.click();
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ items: [
+ {
+ xtype: 'component',
+ html:
+ '3. ' +
+ gettext('Print as paperkey, laminated and placed in secure vault.'),
+ flex: 1,
+ },
+ {
+ xtype: 'button',
+ text: gettext('Print Key'),
+ iconCls: 'fa fa-print x-btn-icon-el-default-toolbar-small',
+ cls: 'x-btn-default-toolbar-small proxmox-inline-button',
+ width: 110,
+ handler: function (b) {
+ let win = this.up('window');
+ win.paperkey(win.key);
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ xtype: 'component',
+ border: false,
+ padding: '10 10 10 10',
+ userCls: 'pmx-hint',
+ html: gettext(
+ 'Please save the encryption key - losing it will render any backup created with it unusable',
+ ),
+ },
+ ],
+ buttons: [
+ {
+ text: gettext('Close'),
+ handler: function (b) {
+ let win = this.up('window');
+ win.close();
+ },
+ },
+ ],
+ paperkey: function (keyString) {
+ let me = this;
+
+ const key = JSON.parse(keyString);
+
+ const qrwidth = 500;
+ let qrdiv = document.createElement('div');
+ let qrcode = new QRCode(qrdiv, {
+ width: qrwidth,
+ height: qrwidth,
+ correctLevel: QRCode.CorrectLevel.H,
+ });
+ qrcode.makeCode(keyString);
+
+ let shortKeyFP = '';
+ if (key.fingerprint) {
+ shortKeyFP = PMG.Utils.render_pbs_fingerprint(key.fingerprint);
+ }
+
+ let printFrame = document.createElement('iframe');
+ Object.assign(printFrame.style, {
+ position: 'fixed',
+ right: '0',
+ bottom: '0',
+ width: '0',
+ height: '0',
+ border: '0',
+ });
+ const prettifiedKey = JSON.stringify(key, null, 2);
+ const keyQrBase64 = qrdiv.children[0].toDataURL('image/png');
+ const html = `<html><head><script>
+ window.addEventListener('DOMContentLoaded', (ev) => window.print());
+ </script><style>@media print and (max-height: 150mm) {
+ h4, p { margin: 0; font-size: 1em; }
+ }</style></head><body style="padding: 5px;">
+ <h4>Encryption Key - Remote '${me.rid}' (${shortKeyFP})</h4>
+<p style="font-size:1.2em;font-family:monospace;white-space:pre-wrap;overflow-wrap:break-word;">
+-----BEGIN PROXMOX BACKUP KEY-----
+${prettifiedKey}
+-----END PROXMOX BACKUP KEY-----</p>
+ <center><img style="width: 100%; max-width: ${qrwidth}px;" src="${keyQrBase64}"></center>
+ </body></html>`;
+
+ printFrame.src = 'data:text/html;base64,' + btoa(html);
+ document.body.appendChild(printFrame);
+ me.on('destroy', () => document.body.removeChild(printFrame));
+ },
+});
+
Ext.define('PMG.panel.PBSEncryptionKeyTab', {
extend: 'Proxmox.panel.InputPanel',
xtype: 'pmgPBSEncryptionKeyTab',
@@ -441,6 +639,19 @@ Ext.define('PMG.PBSEdit', {
bodyPadding: 0,
+ apiCallDone: function (success, response, options) {
+ let res = response.result.data;
+ if (!(res && res.config && res.config['encryption-key'])) {
+ return;
+ }
+ let key = res.config['encryption-key'];
+ Ext.create('PMG.PBSKeyShow', {
+ autoShow: true,
+ rid: res.remote,
+ key: key,
+ });
+ },
+
initComponent: function () {
let me = this;
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-gui 14/15] ui: pbs snapshotview: add encryption and verification state
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (12 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-gui 13/15] ui: pbs remote: allow to downloading/print new encryption key Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-docs 15/15] pmgbackup: minimally document support for encrypted backups Stoiko Ivanov
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
follows pve-manager commit:
a4b87129 ("ui: storage content: add encryption and verification columns for PBS")
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
js/PBSSnapshotView.js | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/js/PBSSnapshotView.js b/js/PBSSnapshotView.js
index 82f0f96..774ad26 100644
--- a/js/PBSSnapshotView.js
+++ b/js/PBSSnapshotView.js
@@ -268,10 +268,23 @@ Ext.define('PMG.PBSConfig', {
flex: 1,
},
{
- text: 'Encrypted',
+ text: gettext('Encrypted'),
dataIndex: 'encrypted',
- hidden: true, // FIXME: actually return from API
- renderer: Proxmox.Utils.format_boolean,
+ renderer: PMG.Utils.render_backup_encryption,
+ flex: 1,
+ },
+ {
+ text: gettext('Verify State'),
+ dataIndex: 'verification',
+ renderer: PMG.Utils.render_backup_verification,
+ sorter: {
+ property: 'verification',
+ transform: (value) => {
+ let state = value?.state ?? 'none';
+ let order = PMG.Utils.verificationStateOrder;
+ return order[state] ?? order.__default__;
+ },
+ },
flex: 1,
},
],
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH pmg-docs 15/15] pmgbackup: minimally document support for encrypted backups
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
` (13 preceding siblings ...)
2026-06-03 18:03 ` [PATCH pmg-gui 14/15] ui: pbs snapshotview: add encryption and verification state Stoiko Ivanov
@ 2026-06-03 18:03 ` Stoiko Ivanov
14 siblings, 0 replies; 16+ messages in thread
From: Stoiko Ivanov @ 2026-06-03 18:03 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
---
pmgbackup.adoc | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/pmgbackup.adoc b/pmgbackup.adoc
index e2da5ae..5ecabb2 100644
--- a/pmgbackup.adoc
+++ b/pmgbackup.adoc
@@ -132,6 +132,22 @@ Retype new password: ******
The fingerprint is optional, if the certificate of the Proxmox Backup Server
remote is signed by a CA trusted by {pmg}.
+You can also encrypt backups, by autogenerating or providing an encryption key
+for a Proxmox Backup Server remote.
+
+NOTE: Keep a copy of the symmetric encryption key in a secure place, or print
+it out. Without the encryption key will not be able to restore an encrypted
+backup. Alternatively you can add a master public key to a remote config, and
+have the symmetric encryption key, encrypted by the master key and added to
+each backup.
+
+.Example addition of a Proxmox Backup Server remote with autogenerated encryption key.
+----
+# pmgbackup proxmox-backup remote add shared-pbs --datastore public --server backup.cloud-provider.example --user 'pmgbackup@pbs!token' --password --encryption-key autogen
+Enter new password: ******
+Retype new password: ******
+----
+
Additionally, you can configure `prune-settings` for each remote, giving you
flexible control over how many backups should be stored on the Proxmox Backup
Server over a specific period of time.
--
2.47.3
^ permalink raw reply related [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-06-03 18:05 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-03 18:03 [PATCH pve-common/pmg-api/pmg-docs/pmg-gui 00/15] fix #3226: add support for encrypted backups Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 01/15] pbs-client: autogen key: rename old one if existing Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pve-common 02/15] pbs-client: add support for master public key Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 03/15] api: pbs remote: fix delete_password invocation Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 04/15] fix #3226: pbs backup: remote: add encryption key support Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 05/15] pbs: job: add encrypted state to snapshot listing Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 06/15] pbs: job: add verification " Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 07/15] pmgbackup: add encypted and verification state to output Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 08/15] api: pbs remote create/update: return parts of the configuration Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-api 09/15] api: pmgbackup: add master-pubkey properties Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 10/15] pbs: snapshotview: add missing gettext invocations Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 11/15] utils: copy pbs helpers from pve-manager Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 12/15] fix #3326: ui: pbs remote: add encryption tab to edit window Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 13/15] ui: pbs remote: allow to downloading/print new encryption key Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-gui 14/15] ui: pbs snapshotview: add encryption and verification state Stoiko Ivanov
2026-06-03 18:03 ` [PATCH pmg-docs 15/15] pmgbackup: minimally document support for encrypted backups Stoiko Ivanov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox