diff --git a/AppImage/ProxMenux-1.2.2.3-beta.AppImage b/AppImage/ProxMenux-1.2.2.3-beta.AppImage index 4962484b..18bf3b4d 100755 Binary files a/AppImage/ProxMenux-1.2.2.3-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 0291a74a..ebfaf379 100644 --- a/AppImage/ProxMenux-Monitor.AppImage.sha256 +++ b/AppImage/ProxMenux-Monitor.AppImage.sha256 @@ -1 +1 @@ -ab9f30de3517bf2aa0746691e230186e647fb68ad57a298837c7c871700e0c4c ProxMenux-1.2.2.3-beta.AppImage +2a42c1b604a8a42b553c79fe924a33534dfa7314d1f7a9df4d1a4d34861ad32e ProxMenux-1.2.2.3-beta.AppImage diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 6e059210..2c05339c 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -13870,16 +13870,27 @@ def api_pbs_encryption_keyfile_info(): # without their passphrase, so parsing the JSON manually gives us # a hint even for imported scrypt keys the operator brought their # own passphrase for. + # KDF detection covers all three shapes proxmox-backup-client + # writes across versions: + # - modern kdf=none → JSON `null` → Python None + # - legacy kdf=none → string "None" + # - kdf=scrypt → object {"Scrypt": {...}} kdf_hint = 'unknown' try: with open(_PBS_KEYFILE_PATH, 'r') as f: raw = f.read(4096) body = json.loads(raw) - kdf_raw = body.get('kdf') - if isinstance(kdf_raw, str): + # `body.get('kdf', 'MISSING')` distinguishes "field explicitly + # null" (present with JSON null → we want 'none') from "field + # absent" (unlikely but treat as unknown to be safe). + _sentinel = object() + kdf_raw = body.get('kdf', _sentinel) + if kdf_raw is None: + kdf_hint = 'none' + elif isinstance(kdf_raw, str): + # "None" → "none", any other string mirrors verbatim. kdf_hint = kdf_raw.lower() elif isinstance(kdf_raw, dict): - # scrypt keyfiles carry `"kdf": {"Scrypt": {...}}`. kdf_hint = next(iter(kdf_raw)).lower() if kdf_raw else 'unknown' except (OSError, json.JSONDecodeError, ValueError, StopIteration): pass diff --git a/scripts/backup_restore/backup_host.sh b/scripts/backup_restore/backup_host.sh index 21079ec9..7860aa72 100755 --- a/scripts/backup_restore/backup_host.sh +++ b/scripts/backup_restore/backup_host.sh @@ -644,16 +644,28 @@ _bk_local_target_usb_submenu() { _bk_manage_destinations() { while true; do + # Option 4 (keyfile management) only appears when a keyfile is + # actually installed on disk. Without one there's nothing to + # manage — the keyfile gets materialised during job creation + # (Generate / Import flow), not from this destinations menu. + # Once the first encrypted job is created, this entry becomes + # visible on the next open. + local -a menu_items=( + 1 "$(translate "Proxmox Backup Server (PBS) destinations")" + 2 "$(translate "Borg repositories")" + 3 "$(translate "Local archive targets (paths + USB mount/unmount)")" + ) + if [[ -s "$HB_STATE_DIR/pbs-key.conf" ]]; then + menu_items+=(4 "$(translate "PBS encryption keyfile (show / replace / remove)")") + fi + menu_items+=(0 "$(translate "Return")") + local choice choice=$(dialog --backtitle "ProxMenux" \ --title "$(translate "Configure backup destinations")" \ --menu "\n$(translate "Pre-configure destinations so you don't have to enter them every time you back up.")" \ "$HB_UI_MENU_H" "$HB_UI_MENU_W" "$HB_UI_MENU_LIST" \ - 1 "$(translate "Proxmox Backup Server (PBS) destinations")" \ - 2 "$(translate "Borg repositories")" \ - 3 "$(translate "Local archive targets (paths + USB mount/unmount)")" \ - 4 "$(translate "PBS encryption keyfile (show / replace / remove)")" \ - 0 "$(translate "Return")" \ + "${menu_items[@]}" \ 3>&1 1>&2 2>&3) || break case "$choice" in @@ -727,21 +739,37 @@ _bk_manage_pbs_encryption_keyfile() { | grep -oE '"fingerprint"[[:space:]]*:[[:space:]]*"[^"]*"' \ | head -1 | sed 's/.*"\([^"]*\)".*/\1/') fi - if grep -q '"kdf":[[:space:]]*"None"' "$key_file" 2>/dev/null; then + # KDF detection covers both keyfile shapes: modern PBS writes + # `"kdf":null` for kdf=none keys; the older format used + # `"kdf":"None"`. Anything else is treated as scrypt. + if grep -qE '"kdf":[[:space:]]*(null|"None")' "$key_file" 2>/dev/null; then kdf_hint="none" elif grep -q '"Scrypt"' "$key_file" 2>/dev/null; then kdf_hint="scrypt" fi installed_at=$(date -r "$key_file" '+%Y-%m-%d %H:%M' 2>/dev/null || echo "") - preview="$(hb_translate "Installed:") \Zb\Z2$(hb_translate "yes")\Zn"$'\n' + # Colors chosen for readability on the light-gray dialog + # background: \Z4 (blue) reads well; \Z2 (bright green) gets + # washed out. + preview="$(hb_translate "Installed:") \Zb\Z4$(hb_translate "yes")\Zn"$'\n' preview+="$(hb_translate "Path:") $key_file"$'\n' [[ -n "$installed_at" ]] && preview+="$(hb_translate "Installed at:") $installed_at"$'\n' preview+="$(hb_translate "KDF:") $kdf_hint"$'\n' [[ -n "$fp" ]] && preview+="$(hb_translate "Fingerprint:") $fp"$'\n' preview+="$(hb_translate "Keyfile passphrase stored:") " - [[ -s "$pass_file" ]] && preview+="\Zb\Z2$(hb_translate "yes")\Zn"$'\n' || preview+="\Zb$(hb_translate "no")\Zn"$'\n' + [[ -s "$pass_file" ]] && preview+="\Zb\Z4$(hb_translate "yes")\Zn"$'\n' || preview+="\Zb$(hb_translate "no")\Zn"$'\n' preview+="$(hb_translate "Recovery escrow present:") " - [[ -s "$recovery_enc" ]] && preview+="\Zb\Z2$(hb_translate "yes")\Zn"$'\n' || preview+="\Zb$(hb_translate "no")\Zn"$'\n' + [[ -s "$recovery_enc" ]] && preview+="\Zb\Z4$(hb_translate "yes")\Zn"$'\n' || preview+="\Zb\Z1$(hb_translate "no")\Zn"$'\n' + # Explicit "the key IS recoverable" line so an operator glancing + # at "KDF: unknown" or a missing passphrase field doesn't infer + # that the key has been lost. Recovery only requires the escrow + # blob on PBS and its passphrase — neither of which lives in + # this on-disk keyfile. + if [[ -s "$recovery_enc" ]]; then + preview+="$(hb_translate "Recoverable via PBS escrow:") \Zb\Z4$(hb_translate "yes")\Zn"$'\n' + else + preview+="$(hb_translate "Recoverable via PBS escrow:") \Zb\Z1$(hb_translate "no")\Zn — $(hb_translate "set a recovery passphrase to enable recovery on a fresh host")"$'\n' + fi else preview="$(hb_translate "No keyfile installed on this host.")"$'\n\n' preview+="$(hb_translate "PBS encrypted backups won't work until one is generated or imported. Both actions happen during job creation the first time you tick 'Encrypt backups'.")"