mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-07-26 18:38:30 +00:00
update 1.2.2.2 beta
This commit is contained in:
@@ -1989,6 +1989,11 @@ function CreateJobDialog({
|
||||
// Backend-specific fields
|
||||
const [pbsRepository, setPbsRepository] = useState<string>("")
|
||||
const [pbsBackupId, setPbsBackupId] = useState<string>("")
|
||||
// Explicit acknowledgement to proceed with encryption but WITHOUT a
|
||||
// recovery passphrase. Mirrors the shell's 3-way gate: without this
|
||||
// flag (or a filled passphrase pair), submit is blocked when the
|
||||
// operator ticks "Encrypt". Same rationale as ManualBackup below.
|
||||
const [pbsAcceptedNoRecovery, setPbsAcceptedNoRecovery] = useState<boolean>(false)
|
||||
// Client-side PBS encryption — sent as `pbs_encrypt` in the payload.
|
||||
// Backend resolves the shared keyfile + injects PBS_KEYFILE into
|
||||
// the job .env so the runner adds `--keyfile` to the backup call.
|
||||
@@ -2182,6 +2187,7 @@ function CreateJobDialog({
|
||||
setPbsRecoveryPass("")
|
||||
setPbsRecoveryPass2("")
|
||||
setPbsRecoveryChange(false)
|
||||
setPbsAcceptedNoRecovery(false)
|
||||
setLocalDestDir("")
|
||||
setBorgRepoSelected("")
|
||||
setBorgPassphrase("")
|
||||
@@ -2268,8 +2274,14 @@ function CreateJobDialog({
|
||||
? true
|
||||
: /* borg */ !!borgRepoSelected
|
||||
|
||||
// PBS encryption 3-way gate — see ManualBackup for full rationale.
|
||||
const pbsRecoveryOk = !(backend === "pbs" && pbsEncrypt) ||
|
||||
(pbsRecoveryStatus?.has_recovery && !pbsRecoveryChange) ||
|
||||
(pbsRecoveryPass !== "" && pbsRecoveryPass === pbsRecoveryPass2) ||
|
||||
pbsAcceptedNoRecovery
|
||||
|
||||
const canSubmit =
|
||||
canAdvanceFrom1 && canAdvanceFrom2 && canAdvanceFrom3 && canAdvanceFrom4 && backendValid
|
||||
canAdvanceFrom1 && canAdvanceFrom2 && canAdvanceFrom3 && canAdvanceFrom4 && backendValid && pbsRecoveryOk
|
||||
|
||||
async function handleCreate() {
|
||||
if (!canSubmit) return
|
||||
@@ -2962,6 +2974,24 @@ function CreateJobDialog({
|
||||
<p className="text-[11px] text-red-400 mt-1">Passphrases don't match.</p>
|
||||
)}
|
||||
</div>
|
||||
{!pbsRecoveryPass && !pbsRecoveryPass2 && (
|
||||
<div className="mt-2 rounded-md border border-red-500/40 bg-red-500/10 p-2.5 space-y-1.5">
|
||||
<div className="flex items-start gap-1.5 text-[11px] text-red-300">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<span className="font-medium">Without a recovery passphrase, the keyfile lives ONLY on this host.</span> If this host is lost or reinstalled, every encrypted backup becomes unrecoverable.
|
||||
</div>
|
||||
</div>
|
||||
<Label className="flex items-center gap-2 text-[11px] cursor-pointer">
|
||||
<Checkbox
|
||||
id="pbsSchedAcceptNoRec"
|
||||
checked={pbsAcceptedNoRecovery}
|
||||
onCheckedChange={(v) => setPbsAcceptedNoRecovery(!!v)}
|
||||
/>
|
||||
<span>I understand the risk and want to encrypt without recovery.</span>
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
{pbsRecoveryStatus?.has_recovery && pbsRecoveryChange && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -3312,6 +3342,11 @@ function ManualBackupDialog({
|
||||
// passphrase if one is already saved — only show the inputs when
|
||||
// there's no escrow yet OR the operator explicitly asks to change.
|
||||
const [pbsRecoveryChange, setPbsRecoveryChange] = useState<boolean>(false)
|
||||
// Explicit acknowledgement to proceed with encryption but WITHOUT a
|
||||
// recovery passphrase. Mirrors the shell's 3-way gate: without this
|
||||
// flag (or a filled passphrase pair), submit is blocked when the
|
||||
// operator ticks "Encrypt".
|
||||
const [pbsAcceptedNoRecovery, setPbsAcceptedNoRecovery] = useState<boolean>(false)
|
||||
// Whether the host already has an escrow blob configured. When
|
||||
// present, the passphrase input becomes optional ("leave blank to
|
||||
// keep saved"). Refreshed after a successful setup call.
|
||||
@@ -3371,6 +3406,7 @@ function ManualBackupDialog({
|
||||
setPbsRecoveryPass("")
|
||||
setPbsRecoveryPass2("")
|
||||
setPbsRecoveryChange(false)
|
||||
setPbsAcceptedNoRecovery(false)
|
||||
setLocalDestDir("")
|
||||
setBorgRepoSelected("")
|
||||
setBorgPassphrase("")
|
||||
@@ -3408,7 +3444,16 @@ function ManualBackupDialog({
|
||||
// Borg destination carries its own passphrase + encryption.
|
||||
// No need to gate on local state — the wizard no longer asks.
|
||||
: !!borgRepoSelected
|
||||
const canSubmit = canAdvanceFrom1 && backendValid
|
||||
// Recovery gate for PBS encryption. When "Encrypt" is ticked, either
|
||||
// a matching passphrase pair OR an explicit accept-risk checkbox is
|
||||
// required — otherwise submit stays blocked. Mirrors the shell 3-way
|
||||
// gate so the operator can never silently create an encrypted backup
|
||||
// whose keyfile only lives on this host.
|
||||
const pbsRecoveryOk = !(backend === "pbs" && pbsEncrypt) ||
|
||||
(pbsRecoveryStatus?.has_recovery && !pbsRecoveryChange) ||
|
||||
(pbsRecoveryPass !== "" && pbsRecoveryPass === pbsRecoveryPass2) ||
|
||||
pbsAcceptedNoRecovery
|
||||
const canSubmit = canAdvanceFrom1 && backendValid && pbsRecoveryOk
|
||||
|
||||
async function handleRun() {
|
||||
if (!canSubmit) return
|
||||
@@ -3723,6 +3768,24 @@ function ManualBackupDialog({
|
||||
Cancel change — keep the saved passphrase
|
||||
</button>
|
||||
)}
|
||||
{!pbsRecoveryPass && !pbsRecoveryPass2 && (
|
||||
<div className="mt-2 rounded-md border border-red-500/40 bg-red-500/10 p-2.5 space-y-1.5">
|
||||
<div className="flex items-start gap-1.5 text-[11px] text-red-300">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<span className="font-medium">Without a recovery passphrase, the keyfile lives ONLY on this host.</span> If this host is lost or reinstalled, every encrypted backup becomes unrecoverable.
|
||||
</div>
|
||||
</div>
|
||||
<Label className="flex items-center gap-2 text-[11px] cursor-pointer">
|
||||
<Checkbox
|
||||
id="pbsManualAcceptNoRec"
|
||||
checked={pbsAcceptedNoRecovery}
|
||||
onCheckedChange={(v) => setPbsAcceptedNoRecovery(!!v)}
|
||||
/>
|
||||
<span>I understand the risk and want to encrypt without recovery.</span>
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -5189,9 +5252,20 @@ function AddDestinationDialog({
|
||||
<p className="text-[11px] text-red-400 mt-1">Passphrases don't match.</p>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] text-amber-400/90">
|
||||
⚠ Without this passphrase the repo cannot be decrypted — losing it means losing every backup. The passphrase is saved server-side at <code className="font-mono">borg-pass-{name || "<name>"}.txt</code> (chmod 0600) so jobs against this destination can use it transparently.
|
||||
</p>
|
||||
<div className="mt-1 rounded-md border border-red-500/40 bg-red-500/10 p-2.5">
|
||||
<div className="flex items-start gap-1.5 text-[11px] text-red-300">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
||||
<div className="space-y-1">
|
||||
<p>
|
||||
<span className="font-medium">This passphrase is the ONLY way to access encrypted Borg backups.</span> It's saved server-side at <code className="font-mono">borg-pass-{name || "<name>"}.txt</code> (chmod 0600) so jobs can use it transparently.
|
||||
</p>
|
||||
<p>
|
||||
If you lose or reinstall this host without a copy of the passphrase somewhere else (password manager, offline note, another host, USB stick...), every encrypted archive in this repository becomes <span className="font-semibold">UNRECOVERABLE</span>.
|
||||
</p>
|
||||
<p className="font-medium">Save the passphrase somewhere safe NOW, before continuing.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1131,11 +1131,34 @@ hb_ask_pbs_encryption() {
|
||||
msg_ok "$(hb_translate "Encryption key created:") $key_file"
|
||||
HB_PBS_KEYFILE_OPT="--keyfile $key_file"
|
||||
|
||||
# Offer to set up automatic PBS-based recovery for the
|
||||
# keyfile. The operator can decline if they want to handle
|
||||
# offsite escrow manually, but the default flow nudges them
|
||||
# to enable it.
|
||||
hb_pbs_setup_recovery || true
|
||||
# Set up automatic PBS-based recovery for the keyfile.
|
||||
# Cancelling the passphrase used to fall through with
|
||||
# `|| true` and leave the backup encrypted with a keyfile
|
||||
# that only lived on this host — one reinstall away from
|
||||
# being unrecoverable. Now we force an explicit choice:
|
||||
# set the passphrase, accept the risk, or cancel encryption.
|
||||
while ! hb_pbs_setup_recovery; do
|
||||
local _rec_choice
|
||||
_rec_choice=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(hb_translate "Recovery passphrase required")" \
|
||||
--menu "$(hb_translate "You chose to encrypt this backup. A recovery passphrase protects the keyfile: an encrypted copy is uploaded to PBS with every backup, and the restore flow can retrieve it if this host is ever lost.")"$'\n\n'"$(hb_translate "Without a recovery passphrase, the keyfile lives ONLY on this host. Losing it (fresh install, disk failure, ...) makes every encrypted backup UNRECOVERABLE.")"$'\n\n'"$(hb_translate "What do you want to do?")" \
|
||||
20 82 3 \
|
||||
"1" "$(hb_translate "Set a recovery passphrase (recommended)")" \
|
||||
"2" "$(hb_translate "Continue WITHOUT recovery — I accept the risk")" \
|
||||
"3" "$(hb_translate "Cancel encryption — backup will not be encrypted")" \
|
||||
3>&1 1>&2 2>&3)
|
||||
# ESC / empty → treat as "retry passphrase" (safest default)
|
||||
[[ -z "$_rec_choice" ]] && _rec_choice="1"
|
||||
case "$_rec_choice" in
|
||||
1) continue ;;
|
||||
2) msg_warn "$(hb_translate "Encryption without recovery — the keyfile lives only on this host. Export it manually to a safe location.")"
|
||||
break ;;
|
||||
3) rm -f "$key_file" 2>/dev/null
|
||||
HB_PBS_KEYFILE_OPT=""
|
||||
msg_info "$(hb_translate "Encryption cancelled — backup will proceed unencrypted.")"
|
||||
break ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
# Surface the actual error from proxmox-backup-client to the
|
||||
# operator — silent failures here were the reason the user
|
||||
@@ -1269,6 +1292,13 @@ hb_prepare_borg_passphrase() {
|
||||
chmod 600 "$sel_pass_file"
|
||||
export BORG_PASSPHRASE="$sel_pass"
|
||||
export BORG_ENCRYPT_MODE="repokey"
|
||||
# Same mandatory acknowledgement as the fresh-target path:
|
||||
# the passphrase is the only escape route to the encrypted
|
||||
# repo. See the comment on the identical dialog below.
|
||||
dialog --backtitle "ProxMenux" --colors \
|
||||
--title "$(hb_translate "IMPORTANT — Save this passphrase")" \
|
||||
--msgbox "\Zb\Z1$(hb_translate "This passphrase is the ONLY way to access encrypted Borg backups.")\Zn"$'\n\n'"$(hb_translate "ProxMenux saved it locally at:")"$'\n'" \Zb${sel_pass_file}\ZB"$'\n\n'"$(hb_translate "If you lose or reinstall this host without a copy of the passphrase somewhere else (password manager, offline note, another host, USB stick...), every encrypted archive in this Borg repository becomes UNRECOVERABLE.")"$'\n\n'"\Zb$(hb_translate "Save the passphrase somewhere safe NOW, before continuing.")\ZB" \
|
||||
18 82
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -1309,6 +1339,20 @@ hb_prepare_borg_passphrase() {
|
||||
target_pass_file="$HB_STATE_DIR/borg-pass-${HB_BORG_LAST_SAVED_NAME}.txt"
|
||||
printf '%s' "$pass1" > "$target_pass_file"
|
||||
chmod 600 "$target_pass_file"
|
||||
|
||||
# Borg repokey stores the encrypted repo key inside the repo
|
||||
# itself, so the passphrase IS the only thing the operator has
|
||||
# to keep to be able to restore from a fresh host. The two
|
||||
# passphrase prompts above feel like routine setup; without
|
||||
# this block, operators reinstall their host expecting things
|
||||
# to Just Work and then discover the passphrase file is gone
|
||||
# and every encrypted archive is unreadable. Loud, mandatory
|
||||
# acknowledgement: they have to press OK on a screen that says
|
||||
# exactly what's at stake.
|
||||
dialog --backtitle "ProxMenux" --colors \
|
||||
--title "$(hb_translate "IMPORTANT — Save this passphrase")" \
|
||||
--msgbox "\Zb\Z1$(hb_translate "This passphrase is the ONLY way to access encrypted Borg backups.")\Zn"$'\n\n'"$(hb_translate "ProxMenux saved it locally at:")"$'\n'" \Zb${target_pass_file}\ZB"$'\n\n'"$(hb_translate "If you lose or reinstall this host without a copy of the passphrase somewhere else (password manager, offline note, another host, USB stick...), every encrypted archive in this Borg repository becomes UNRECOVERABLE.")"$'\n\n'"\Zb$(hb_translate "Save the passphrase somewhere safe NOW, before continuing.")\ZB" \
|
||||
18 82
|
||||
export BORG_PASSPHRASE="$pass1"
|
||||
export BORG_ENCRYPT_MODE="repokey"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user