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.
-
- 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.
+
+
+ {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.
+
+ 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.
-
+ 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.
+ {/* 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.
+