From 4628e67e5c297b6b89c3f97594ae148e85b05f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20R=C3=B6gl-Brunner?= Date: Thu, 29 Jan 2026 13:55:53 +0100 Subject: [PATCH] Fix PBS certificate validation: pass PBS_FINGERPRINT, optional fingerprint for trusted CA - Pass stored pbs_fingerprint as PBS_FINGERPRINT in login, snapshot list, and restore - Allow empty fingerprint so trusted-CA PBS works without entering one - Make fingerprint field optional in PBSCredentialsModal with updated helper text Fixes #438 --- src/app/_components/PBSCredentialsModal.tsx | 9 ++++----- src/server/services/backupService.ts | 22 +++++++++++++-------- src/server/services/restoreService.ts | 11 +++++++++-- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/app/_components/PBSCredentialsModal.tsx b/src/app/_components/PBSCredentialsModal.tsx index c6c36cd..6eee7a4 100644 --- a/src/app/_components/PBSCredentialsModal.tsx +++ b/src/app/_components/PBSCredentialsModal.tsx @@ -270,22 +270,21 @@ export function PBSCredentialsModal({ htmlFor="pbs-fingerprint" className="text-foreground mb-1 block text-sm font-medium" > - Fingerprint * + Fingerprint setPbsFingerprint(e.target.value)} - required disabled={isLoading} className="bg-card text-foreground placeholder-muted-foreground focus:ring-ring focus:border-ring border-border w-full rounded-md border px-3 py-2 shadow-sm focus:ring-2 focus:outline-none" placeholder="e.g., 7b:e5:87:38:5e:16:05:d1:12:22:7f:73:d2:e2:d0:cf:8c:cb:28:e2:74:0c:78:91:1a:71:74:2e:79:20:5a:02" />

- Server fingerprint for auto-acceptance. You can find this on - your PBS dashboard by clicking the "Show Fingerprint" - button. + Leave empty if PBS uses a trusted CA (e.g. Let's Encrypt). + For self-signed certificates, enter the server fingerprint from + the PBS dashboard ("Show Fingerprint").

diff --git a/src/server/services/backupService.ts b/src/server/services/backupService.ts index a64678a..a615331 100644 --- a/src/server/services/backupService.ts +++ b/src/server/services/backupService.ts @@ -327,13 +327,16 @@ class BackupService { // PBS supports PBS_PASSWORD and PBS_REPOSITORY environment variables for non-interactive login const repository = `root@pam@${pbsIp}:${pbsDatastore}`; - // Escape password for shell safety (single quotes) + // Escape password and fingerprint for shell safety (single quotes) const escapedPassword = credential.pbs_password.replace(/'/g, "'\\''"); - - // Use PBS_PASSWORD environment variable for non-interactive authentication - // Auto-accept fingerprint by piping "y" to stdin - // PBS will use PBS_PASSWORD env var if available, avoiding interactive prompt - const fullCommand = `echo "y" | PBS_PASSWORD='${escapedPassword}' PBS_REPOSITORY='${repository}' timeout 10 proxmox-backup-client login --repository ${repository} 2>&1`; + const fingerprint = credential.pbs_fingerprint?.trim() ?? ''; + const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : ''; + const envParts = [`PBS_PASSWORD='${escapedPassword}'`, `PBS_REPOSITORY='${repository}'`]; + if (escapedFingerprint) { + envParts.push(`PBS_FINGERPRINT='${escapedFingerprint}'`); + } + const envStr = envParts.join(' '); + const fullCommand = `${envStr} timeout 10 proxmox-backup-client login --repository ${repository} 2>&1`; console.log(`[BackupService] Logging into PBS: ${repository}`); @@ -419,9 +422,12 @@ class BackupService { // Build full repository string: root@pam@: const repository = `root@pam@${pbsIp}:${pbsDatastore}`; - + const fingerprint = credential.pbs_fingerprint?.trim() ?? ''; + const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : ''; + const snapshotEnvParts = escapedFingerprint ? [`PBS_FINGERPRINT='${escapedFingerprint}'`] : []; + const snapshotEnvStr = snapshotEnvParts.length ? snapshotEnvParts.join(' ') + ' ' : ''; // Use correct command: snapshot list ct/ --repository - const command = `timeout 30 proxmox-backup-client snapshot list ct/${ctId} --repository ${repository} 2>&1 || echo "PBS_ERROR"`; + const command = `${snapshotEnvStr}timeout 30 proxmox-backup-client snapshot list ct/${ctId} --repository ${repository} 2>&1 || echo "PBS_ERROR"`; let output = ''; console.log(`[BackupService] Discovering PBS backups for CT ${ctId} on repository ${repository}`); diff --git a/src/server/services/restoreService.ts b/src/server/services/restoreService.ts index db5014d..f855794 100644 --- a/src/server/services/restoreService.ts +++ b/src/server/services/restoreService.ts @@ -250,9 +250,16 @@ class RestoreService { const targetFolder = `/var/lib/vz/dump/vzdump-lxc-${ctId}-${snapshotNameForPath}`; const targetTar = `${targetFolder}.tar`; - // Use PBS_PASSWORD env var and add timeout for long downloads + // Use PBS_PASSWORD env var and add timeout for long downloads; PBS_FINGERPRINT when set for cert validation const escapedPassword = credential.pbs_password.replace(/'/g, "'\\''"); - const restoreCommand = `PBS_PASSWORD='${escapedPassword}' PBS_REPOSITORY='${repository}' timeout 300 proxmox-backup-client restore "${snapshotPath}" root.pxar "${targetFolder}" --repository '${repository}' 2>&1`; + const fingerprint = credential.pbs_fingerprint?.trim() ?? ''; + const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : ''; + const restoreEnvParts = [`PBS_PASSWORD='${escapedPassword}'`, `PBS_REPOSITORY='${repository}'`]; + if (escapedFingerprint) { + restoreEnvParts.push(`PBS_FINGERPRINT='${escapedFingerprint}'`); + } + const restoreEnvStr = restoreEnvParts.join(' '); + const restoreCommand = `${restoreEnvStr} timeout 300 proxmox-backup-client restore "${snapshotPath}" root.pxar "${targetFolder}" --repository '${repository}' 2>&1`; let output = ''; let exitCode = 0;