diff --git a/AppImage/ProxMenux-1.2.2.2-beta.AppImage b/AppImage/ProxMenux-1.2.2.3-beta.AppImage similarity index 91% rename from AppImage/ProxMenux-1.2.2.2-beta.AppImage rename to AppImage/ProxMenux-1.2.2.3-beta.AppImage index 265444e0..61a48343 100755 Binary files a/AppImage/ProxMenux-1.2.2.2-beta.AppImage and b/AppImage/ProxMenux-1.2.2.3-beta.AppImage differ diff --git a/AppImage/ProxMenux-Monitor.AppImage.sha256 b/AppImage/ProxMenux-Monitor.AppImage.sha256 index 0881c0c6..08f1df63 100644 --- a/AppImage/ProxMenux-Monitor.AppImage.sha256 +++ b/AppImage/ProxMenux-Monitor.AppImage.sha256 @@ -1 +1 @@ -08b669193097449f8330ad430066574bf0e99f477aea97172e6b845ad4ee648c ProxMenux-1.2.2.2-beta.AppImage +4abe67f2d2d7516c438d222ed73fb166b0f6b3b77e85e9a6be37194b4b178cdd ProxMenux-1.2.2.3-beta.AppImage diff --git a/AppImage/components/host-backup.tsx b/AppImage/components/host-backup.tsx index aab0eff3..deabe18c 100644 --- a/AppImage/components/host-backup.tsx +++ b/AppImage/components/host-backup.tsx @@ -46,6 +46,7 @@ import { History, } from "lucide-react" import { ScriptTerminalModal } from "./script-terminal-modal" +import { RestoreProgressCard } from "./restore-progress-card" import { fetchApi, getApiUrl } from "../lib/api-config" import { fetchTerminalTicket } from "../lib/terminal-ws" import { formatStorage, formatBytes } from "../lib/utils" @@ -415,6 +416,12 @@ export function HostBackup() { return (
+ {/* ── Post-restore progress card ──────────────────── + Renders only while a restore is running or its + summary hasn't been acknowledged. Once dismissed, + it collapses to a "Past restores" ghost button. */} + + {/* ── Scheduled jobs ───────────────────────────────── */} @@ -2347,12 +2354,21 @@ function CreateJobDialog({ if (mode === "attach" && !selectedPveJob) return setSubmitting(true) setError(null) - // "Import existing" mode: push the user-supplied keyfile to the - // canonical shared path BEFORE anything else. This mirrors the - // shell wizard where hb_pbs_import_keyfile runs before recovery - // setup and before the job .env is written. If the upload fails - // we bail loudly instead of silently downgrading to "generate new". - if (backend === "pbs" && pbsEncryptMode === "existing") { + // Encryption submit paths: + // • enabled + keyfile already on disk → mode = "existing", no upload + // (backend reuses the canonical file at _PBS_KEYFILE_PATH). + // • enabled + no keyfile + Generate → mode = "new", backend creates it. + // • enabled + no keyfile + Import → upload the file first, then + // mode = "existing" so the backend uses the freshly-installed key. + // + // Only the third branch actually needs to POST to + // /api/host-backups/pbs-encryption/import — the second is handled + // server-side, and the first has nothing to upload. + const needsImport = + backend === "pbs" && + pbsEncryptMode === "existing" && + !pbsRecoveryStatus?.has_keyfile + if (needsImport) { if (!pbsImportFile) { setError("Pick a keyfile to import.") setSubmitting(false) @@ -2995,51 +3011,100 @@ function CreateJobDialog({ PBS organises backups into named groups, each with its own retention. Leave blank to use the automatic default for this host (recommended).

- {/* PBS client-side encryption — same flow as the - shell wizard. The keyfile can be freshly generated - per host or imported from an existing one the - operator uses across every host. */} + {/* PBS client-side encryption — mirrors the shell wizard: + step 1 is a plain yes/no, step 2 only appears when + no keyfile is installed yet. Once a keyfile exists, + every future encrypted job silently reuses it. */}
-
- - -

- Adds --keyfile to proxmox-backup-client backup. Encryption happens before upload so chunks land on PBS already ciphered. The keyfile is installed at /usr/local/share/proxmenux/pbs-key.conf (chmod 0600) and reused by every encrypted PBS job on this host. -

-
- {pbsEncryptMode === "existing" && ( -
- - setPbsImportFile(e.target.files?.[0] ?? null)} - disabled={pbsImportBusy} - className="h-8 text-[11px]" - /> -

- The file is validated with proxmox-backup-client key info before being installed. If validation fails the job is not created and the existing keyfile (if any) stays in place. +

+
+ + Encrypt backups (client-side keyfile) +
+

+ Adds --keyfile to proxmox-backup-client backup. Encryption happens before upload so chunks land on PBS already ciphered. The keyfile is installed at /usr/local/share/proxmenux/pbs-key.conf (chmod 0600) and reused by every encrypted PBS job on this host.

+ + + {pbsEncrypt && pbsRecoveryStatus?.has_keyfile && ( +
+ +
+ An encryption key is already installed on this host — it will be reused for this job. To replace it, first import a new one from Settings → Host backup or remove the existing keyfile. +
+
)} + + {pbsEncrypt && !pbsRecoveryStatus?.has_keyfile && ( +
+
+ No encryption key is stored on this host. Choose how to set one up: +
+ + + {pbsEncryptMode === "existing" && ( +
+ + setPbsImportFile(e.target.files?.[0] ?? null)} + disabled={pbsImportBusy} + className="h-8 text-[11px]" + /> +

+ Validated with proxmox-backup-client key info before install. On validation failure the job is not created and no keyfile is written. +

+
+ )} +
+ )} + {pbsEncrypt && (
@@ -3562,10 +3627,14 @@ function ManualBackupDialog({ if (!canSubmit) return setSubmitting(true) setError(null) - // "Import existing" mode: same shape as CreateJobDialog — push the - // keyfile to the canonical path before anything else, bail loudly - // if validation fails so we don't silently fall back to "new". - if (backend === "pbs" && pbsEncryptMode === "existing") { + // Same three encryption submit paths as CreateJobDialog. Only + // "Import + no keyfile installed yet" actually uploads; the on-disk + // reuse case sends mode=existing without touching the file. + const needsImport = + backend === "pbs" && + pbsEncryptMode === "existing" && + !pbsRecoveryStatus?.has_keyfile + if (needsImport) { if (!pbsImportFile) { setError("Pick a keyfile to import.") setSubmitting(false) @@ -3827,46 +3896,91 @@ function ManualBackupDialog({ />
-
- - -

- Uses the shared keyfile at /usr/local/share/proxmenux/pbs-key.conf. -

-
- {pbsEncryptMode === "existing" && ( -
- - setPbsImportFile(e.target.files?.[0] ?? null)} - disabled={pbsImportBusy} - className="h-8 text-[11px]" - /> -

- Validated with proxmox-backup-client key info before install. +

+
+ + Encrypt this backup (client-side keyfile) +
+

+ Uses the shared keyfile at /usr/local/share/proxmenux/pbs-key.conf.

+ + + {pbsEncrypt && pbsRecoveryStatus?.has_keyfile && ( +
+ +
+ An encryption key is already installed on this host — it will be reused for this backup. +
+
)} + + {pbsEncrypt && !pbsRecoveryStatus?.has_keyfile && ( +
+
+ No encryption key is stored on this host. Choose how to set one up: +
+ + + {pbsEncryptMode === "existing" && ( +
+ + setPbsImportFile(e.target.files?.[0] ?? null)} + disabled={pbsImportBusy} + className="h-8 text-[11px]" + /> +

+ Validated with proxmox-backup-client key info before install. +

+
+ )} +
+ )} + {pbsEncrypt && (
@@ -4436,7 +4550,7 @@ function DestinationsSection({ PBS keyfile is missing — recover it from PBS
- An encrypted recovery copy of the keyfile is available ({pbsRecoveryAvailable!.snapshots.length} snapshot{pbsRecoveryAvailable!.snapshots.length === 1 ? "" : "s"}). Click to rebuild the keyfile using your recovery passphrase. + An encrypted recovery copy of the keyfile is available ({pbsRecoveryAvailable!.snapshots.length} backup{pbsRecoveryAvailable!.snapshots.length === 1 ? "" : "s"}). Click to rebuild the keyfile using your recovery passphrase.
Recover → @@ -7597,6 +7711,17 @@ function RestoreOptionsModal({ {step === "choose" && (
+ {/* Post-restore Monitor hint — the Backups tab renders a live + progress card driven by /var/lib/proxmenux/restore-state.json, + so the operator has a place to watch component reinstalls, + sanity warnings, and the rollback delta after the reboot. */} +
+ +
+ After the reboot, this Backups tab will show a live post-restore progress card with ETA, per-component status, log tail and rollback delta. If Telegram/Discord/ntfy notifications are configured, you'll also receive the "Host restore finished" event. +
+
+ {isBkOlder && (
diff --git a/AppImage/components/login.tsx b/AppImage/components/login.tsx index 04331e1c..2c2c272c 100644 --- a/AppImage/components/login.tsx +++ b/AppImage/components/login.tsx @@ -271,7 +271,7 @@ export function Login({ onLogin }: LoginProps) {
-

ProxMenux Monitor v1.2.2.2-beta

+

ProxMenux Monitor v1.2.2.3-beta

) diff --git a/AppImage/components/proxmox-dashboard.tsx b/AppImage/components/proxmox-dashboard.tsx index 1cbffee5..ac68835e 100644 --- a/AppImage/components/proxmox-dashboard.tsx +++ b/AppImage/components/proxmox-dashboard.tsx @@ -836,7 +836,7 @@ export function ProxmoxDashboard() {