mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-08-01 13:26:21 +00:00
host-backup(pbs): fix scheduled-job encryption + surface real key import error + pass --keyfile on downloads
Three bugs against the PBS encryption flow: 1. Create-scheduled-job with encryption failed with "Recovery setup failed: no PBS keyfile present" whenever the operator picked "Generate a new keyfile" but had no keyfile installed yet. The frontend called /pbs-recovery/setup before creating the job, but the keyfile was only materialised later during job creation. The endpoint now generates the keyfile atomically if missing before building the escrow blob — same prompt-first order the CLI wizard applies. Existing keyfiles are still trusted and never rotated. 2. Importing a valid PBS keyfile via the Web dialog returned a generic "did not recognise this file as a valid PBS keyfile" that hid the real reason (kdf mismatch, missing passphrase, corrupt JSON, ...). The endpoint now attaches the stderr of `proxmox-backup-client key info` as `tool_output` and the frontend renders it verbatim inside the red banner. Also strips a leading UTF-8 BOM before validating so an editor-inserted BOM stops being silently classified as "invalid keyfile". 3. Downloading an encrypted PBS snapshot failed with "missing key — manifest was created with key XX:XX:..." even when the correct keyfile was installed at /usr/local/share/proxmenux/pbs-key.conf, because the restore worker invoked `proxmox-backup-client restore` without `--keyfile`. The flag is now passed whenever a local keyfile exists (PBS ignores it for unencrypted archives). On a fingerprint mismatch the error now appends the installed key's fingerprint so it can be compared side-by-side with the manifest's expected value — same fingerprint also exposed via /pbs-recovery/status for the UI. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4b022d05f2
commit
26b47e63d9
@@ -452,7 +452,7 @@ export function HostBackup() {
|
||||
) : jobsResp.jobs.filter((j) => !j.manual).length === 0 ? null : (
|
||||
<div className="space-y-2">
|
||||
{actionError && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{actionError}
|
||||
</div>
|
||||
)}
|
||||
@@ -1419,7 +1419,7 @@ function InspectModal({
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -2389,7 +2389,15 @@ function CreateJobDialog({
|
||||
})
|
||||
mutatePbsRecovery()
|
||||
} catch (e) {
|
||||
setError(`Keyfile import failed: ${e instanceof Error ? e.message : String(e)}`)
|
||||
// The import endpoint returns `{error, tool_output, tool_exit_code}`
|
||||
// on validation failure. Surface the tool output verbatim so the
|
||||
// operator can tell a real "not a keyfile" from a "keyfile needs
|
||||
// a passphrase / uses unsupported KDF" and act on it.
|
||||
const err = e as Error & { body?: { tool_output?: string; tool_exit_code?: number } }
|
||||
const detail = err.body?.tool_output
|
||||
? `${err.message}\n\nproxmox-backup-client output:\n${err.body.tool_output}`
|
||||
: err.message
|
||||
setError(`Keyfile import failed: ${detail || String(e)}`)
|
||||
setPbsImportBusy(false)
|
||||
setSubmitting(false)
|
||||
return
|
||||
@@ -3390,7 +3398,7 @@ function CreateJobDialog({
|
||||
})()}
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -3653,7 +3661,15 @@ function ManualBackupDialog({
|
||||
})
|
||||
mutatePbsRecovery()
|
||||
} catch (e) {
|
||||
setError(`Keyfile import failed: ${e instanceof Error ? e.message : String(e)}`)
|
||||
// The import endpoint returns `{error, tool_output, tool_exit_code}`
|
||||
// on validation failure. Surface the tool output verbatim so the
|
||||
// operator can tell a real "not a keyfile" from a "keyfile needs
|
||||
// a passphrase / uses unsupported KDF" and act on it.
|
||||
const err = e as Error & { body?: { tool_output?: string; tool_exit_code?: number } }
|
||||
const detail = err.body?.tool_output
|
||||
? `${err.message}\n\nproxmox-backup-client output:\n${err.body.tool_output}`
|
||||
: err.message
|
||||
setError(`Keyfile import failed: ${detail || String(e)}`)
|
||||
setPbsImportBusy(false)
|
||||
setSubmitting(false)
|
||||
return
|
||||
@@ -4188,7 +4204,7 @@ function ManualBackupDialog({
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -4527,7 +4543,7 @@ function DestinationsSection({
|
||||
</Button>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -5539,7 +5555,7 @@ function AddDestinationDialog({
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -5905,7 +5921,7 @@ function ExtraPathsSection() {
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -6090,7 +6106,7 @@ function UsbDrivesSection() {
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -6441,7 +6457,7 @@ function JobDetailModal({
|
||||
</DialogHeader>
|
||||
|
||||
{actionError && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{actionError}
|
||||
</div>
|
||||
)}
|
||||
@@ -6818,7 +6834,7 @@ function ManualJobWatchModal({
|
||||
</DialogHeader>
|
||||
|
||||
{actionError && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{actionError}
|
||||
</div>
|
||||
)}
|
||||
@@ -7065,7 +7081,7 @@ function PbsKeyfileRecoveryDialog({
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10">
|
||||
<div className="text-xs text-red-500 px-3 py-2 rounded-md border border-red-500/30 bg-red-500/10 whitespace-pre-wrap break-words">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user