* [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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.