update 1.2.2.3 beta

This commit is contained in:
MacRimi
2026-07-05 23:58:58 +02:00
parent fd1aeb1ead
commit 487ab04a14
8 changed files with 212 additions and 82 deletions

View File

@@ -1 +1 @@
1678cb55256ffcfb62f451b631aa174c014b36319c857b7d066cde8d0431c7f1 ProxMenux-1.2.2.3-beta.AppImage
7eb39d1bb3aed0750f32708fab66bf5c2c95a47da5722b9bd351b34c7c31b3ee ProxMenux-1.2.2.3-beta.AppImage

View File

@@ -2046,6 +2046,7 @@ function CreateJobDialog({
// File picked in the "existing" mode. Kept in a ref-like state so a
// stale reference doesn't linger across modal opens.
const [pbsImportFile, setPbsImportFile] = useState<File | null>(null)
const [pbsImportKeyfilePass, setPbsImportKeyfilePass] = useState<string>("")
const [pbsImportBusy, setPbsImportBusy] = useState<boolean>(false)
// Legacy alias for the many recovery / gating checks below — a truthy
// value means "the operator wants an encrypted backup" regardless of
@@ -2066,7 +2067,7 @@ function CreateJobDialog({
// present, the passphrase input becomes optional ("leave blank to
// keep saved"). Refreshed after a successful setup call.
const { data: pbsRecoveryStatus, mutate: mutatePbsRecovery } = useSWR<{
has_keyfile: boolean; has_recovery: boolean
has_keyfile: boolean; has_recovery: boolean; has_keyfile_passphrase?: boolean
}>(
open && backend === "pbs" ? "/api/host-backups/pbs-recovery/status" : null,
fetcher,
@@ -2246,6 +2247,7 @@ function CreateJobDialog({
setPbsBackupId("")
setPbsEncryptMode("none")
setPbsImportFile(null)
setPbsImportKeyfilePass("")
setPbsImportBusy(false)
setPbsRecoveryPass("")
setPbsRecoveryPass2("")
@@ -2385,7 +2387,10 @@ function CreateJobDialog({
await fetchApi("/api/host-backups/pbs-encryption/import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content_b64 }),
body: JSON.stringify({
content_b64,
keyfile_passphrase: pbsImportKeyfilePass,
}),
})
mutatePbsRecovery()
} catch (e) {
@@ -3093,21 +3098,40 @@ function CreateJobDialog({
</div>
</label>
{pbsEncryptMode === "existing" && (
<div className="space-y-1.5 pt-1">
<Label htmlFor="pbsImportFile" className="text-[11px] font-medium">
Keyfile to import
</Label>
<Input
id="pbsImportFile"
type="file"
accept=".conf,.key,application/json,text/plain"
onChange={(e) => setPbsImportFile(e.target.files?.[0] ?? null)}
disabled={pbsImportBusy}
className="h-8 text-[11px]"
/>
<p className="text-[10px] text-muted-foreground">
Validated with <code className="font-mono">proxmox-backup-client key info</code> before install. On validation failure the job is not created and no keyfile is written.
</p>
<div className="space-y-2 pt-1">
<div className="space-y-1.5">
<Label htmlFor="pbsImportFile" className="text-[11px] font-medium">
Keyfile to import
</Label>
<Input
id="pbsImportFile"
type="file"
accept=".conf,.key,application/json,text/plain"
onChange={(e) => setPbsImportFile(e.target.files?.[0] ?? null)}
disabled={pbsImportBusy}
className="h-8 text-[11px]"
/>
<p className="text-[10px] text-muted-foreground">
ProxMenux does not validate the file any keyfile you accept as valid on your PBS is accepted here. If it later fails at backup time, the tool's error tells you why.
</p>
</div>
<div className="space-y-1.5">
<Label htmlFor="pbsImportKeyfilePass" className="text-[11px] font-medium">
Keyfile passphrase (optional)
</Label>
<Input
id="pbsImportKeyfilePass"
type="password"
value={pbsImportKeyfilePass}
onChange={(e) => setPbsImportKeyfilePass(e.target.value)}
disabled={pbsImportBusy}
placeholder="Leave blank if the keyfile has no passphrase"
className="h-8 text-[11px] font-mono"
/>
<p className="text-[10px] text-muted-foreground">
If the keyfile was generated with <code className="font-mono">proxmox-backup-client key create --kdf scrypt</code> (the tool's default) it needs the original passphrase to be unlocked at backup time. Leave blank for <code className="font-mono">--kdf none</code> keyfiles. Stored at <code className="font-mono">/usr/local/share/proxmenux/pbs-key.pass</code> (chmod 600).
</p>
</div>
</div>
)}
</div>
@@ -3512,6 +3536,7 @@ function ManualBackupDialog({
// block — the three modes and the import flow are identical here.
const [pbsEncryptMode, setPbsEncryptMode] = useState<"none" | "new" | "existing">("none")
const [pbsImportFile, setPbsImportFile] = useState<File | null>(null)
const [pbsImportKeyfilePass, setPbsImportKeyfilePass] = useState<string>("")
const [pbsImportBusy, setPbsImportBusy] = useState<boolean>(false)
const pbsEncrypt = pbsEncryptMode !== "none"
const [pbsRecoveryPass, setPbsRecoveryPass] = useState<string>("")
@@ -3525,7 +3550,7 @@ function ManualBackupDialog({
// present, the passphrase input becomes optional ("leave blank to
// keep saved"). Refreshed after a successful setup call.
const { data: pbsRecoveryStatus, mutate: mutatePbsRecovery } = useSWR<{
has_keyfile: boolean; has_recovery: boolean
has_keyfile: boolean; has_recovery: boolean; has_keyfile_passphrase?: boolean
}>(
open && backend === "pbs" ? "/api/host-backups/pbs-recovery/status" : null,
fetcher,
@@ -3578,6 +3603,7 @@ function ManualBackupDialog({
setPbsBackupId("")
setPbsEncryptMode("none")
setPbsImportFile(null)
setPbsImportKeyfilePass("")
setPbsImportBusy(false)
setPbsRecoveryPass("")
setPbsRecoveryPass2("")
@@ -3657,7 +3683,10 @@ function ManualBackupDialog({
await fetchApi("/api/host-backups/pbs-encryption/import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content_b64 }),
body: JSON.stringify({
content_b64,
keyfile_passphrase: pbsImportKeyfilePass,
}),
})
mutatePbsRecovery()
} catch (e) {
@@ -3977,21 +4006,40 @@ function ManualBackupDialog({
</div>
</label>
{pbsEncryptMode === "existing" && (
<div className="space-y-1.5 pt-1">
<Label htmlFor="manualPbsImportFile" className="text-[11px] font-medium">
Keyfile to import
</Label>
<Input
id="manualPbsImportFile"
type="file"
accept=".conf,.key,application/json,text/plain"
onChange={(e) => setPbsImportFile(e.target.files?.[0] ?? null)}
disabled={pbsImportBusy}
className="h-8 text-[11px]"
/>
<p className="text-[10px] text-muted-foreground">
Validated with <code className="font-mono">proxmox-backup-client key info</code> before install.
</p>
<div className="space-y-2 pt-1">
<div className="space-y-1.5">
<Label htmlFor="manualPbsImportFile" className="text-[11px] font-medium">
Keyfile to import
</Label>
<Input
id="manualPbsImportFile"
type="file"
accept=".conf,.key,application/json,text/plain"
onChange={(e) => setPbsImportFile(e.target.files?.[0] ?? null)}
disabled={pbsImportBusy}
className="h-8 text-[11px]"
/>
<p className="text-[10px] text-muted-foreground">
ProxMenux does not validate the file any keyfile accepted by your PBS is accepted here.
</p>
</div>
<div className="space-y-1.5">
<Label htmlFor="manualPbsImportKeyfilePass" className="text-[11px] font-medium">
Keyfile passphrase (optional)
</Label>
<Input
id="manualPbsImportKeyfilePass"
type="password"
value={pbsImportKeyfilePass}
onChange={(e) => setPbsImportKeyfilePass(e.target.value)}
disabled={pbsImportBusy}
placeholder="Leave blank if the keyfile has no passphrase"
className="h-8 text-[11px] font-mono"
/>
<p className="text-[10px] text-muted-foreground">
Only needed for keyfiles created with <code className="font-mono">--kdf scrypt</code> (the default of <code className="font-mono">proxmox-backup-client key create</code>). Stored at <code className="font-mono">/usr/local/share/proxmenux/pbs-key.pass</code> (chmod 600) and reused by every encrypted job on this host.
</p>
</div>
</div>
)}
</div>
@@ -4354,7 +4402,7 @@ function DestinationsSection({
// exists. Drives the "Recover keyfile" banner.
const hasPbs = (destinations?.pbs?.length ?? 0) > 0
const { data: pbsRecoveryStatus, mutate: mutatePbsRecovery } = useSWR<{
has_keyfile: boolean; has_recovery: boolean
has_keyfile: boolean; has_recovery: boolean; has_keyfile_passphrase?: boolean
}>(hasPbs ? "/api/host-backups/pbs-recovery/status" : null, fetcher)
const { data: pbsRecoveryAvailable } = useSWR<{
snapshots: Array<{ repo_name: string; repo_repository: string; backup_id: string; source_host: string; backup_time: number; snapshot: string }>

View File

@@ -13738,6 +13738,14 @@ _PBS_KEYFILE_PATH = f'{_BACKUP_STATE_DIR}/pbs-key.conf'
# shell's hb_pbs_setup_recovery flow byte-for-byte (AES-256-CBC +
# PBKDF2 600k iterations).
_PBS_RECOVERY_ENC_PATH = f'{_BACKUP_STATE_DIR}/pbs-key.recovery.enc'
# Passphrase used to unlock the keyfile itself when it was created with
# `--kdf scrypt`. Written by the "Import existing keyfile" flow when
# the operator provides one; empty file (or missing) means the keyfile
# is kdf=none and no passphrase is needed. Read at backup time by the
# runner and exported as PBS_ENCRYPTION_PASSWORD so
# proxmox-backup-client can decrypt the key without prompting.
# Same trust boundary as the keyfile itself: chmod 600, next to it.
_PBS_KEYFILE_PASS_PATH = f'{_BACKUP_STATE_DIR}/pbs-key.pass'
def _pbs_keyfile_get_or_create(create_if_missing: bool = True) -> str:
@@ -13843,6 +13851,13 @@ def api_pbs_recovery_status():
return jsonify({
'has_keyfile': os.path.isfile(_PBS_KEYFILE_PATH),
'has_recovery': os.path.isfile(_PBS_RECOVERY_ENC_PATH),
# True only when a non-empty passphrase file exists next to the
# keyfile. Lets the UI skip re-prompting the operator for the
# scrypt-unlock passphrase during subsequent job creations.
'has_keyfile_passphrase': (
os.path.isfile(_PBS_KEYFILE_PASS_PATH)
and os.path.getsize(_PBS_KEYFILE_PASS_PATH) > 0
),
'keyfile_path': _PBS_KEYFILE_PATH,
'recovery_path': _PBS_RECOVERY_ENC_PATH,
# Fingerprint of the installed keyfile — empty when no keyfile
@@ -14041,12 +14056,18 @@ def api_pbs_encryption_import():
# Strip a leading UTF-8 BOM (b'\xef\xbb\xbf') if the operator's
# editor added one — the byte order mark makes the file no longer
# a valid JSON document and `key info` refuses it with a parse
# error that reads like a generic "not a keyfile", which sends
# the operator down the wrong debugging path.
# a valid JSON document and downstream tools refuse it with a
# cryptic parse error.
if content.startswith(b'\xef\xbb\xbf'):
content = content[3:]
# Optional passphrase for a scrypt-encoded keyfile. Blank means the
# keyfile is kdf=none. We do NOT verify that the passphrase actually
# unlocks the keyfile — that would require the operator to be
# present and the flow to be interactive. If it's wrong, backups
# will fail loudly with proxmox-backup-client's own error message.
keyfile_pass = payload.get('keyfile_passphrase') or ''
import tempfile
tmp_fd, tmp_path = tempfile.mkstemp(prefix='pbs-key.import.', dir=_BACKUP_STATE_DIR)
try:
@@ -14057,37 +14078,26 @@ def api_pbs_encryption_import():
except OSError as e:
return jsonify({'error': f'cannot write tmp keyfile: {e}'}), 500
try:
r = subprocess.run(
['proxmox-backup-client', 'key', 'info', '--output-format', 'json', tmp_path],
capture_output=True, text=True, timeout=10,
)
except (subprocess.TimeoutExpired, OSError) as e:
return jsonify({'error': f'validation failed: {type(e).__name__}'}), 500
if r.returncode != 0:
# Surface the real tool output so the operator can tell a
# bad-file error from a needs-passphrase error (keyfiles
# generated with `--kdf scrypt` need to be decrypted with
# their original passphrase before they can be reused as an
# import — ProxMenux only supports kdf=none for the
# canonical shared keyfile).
tool_output = (r.stderr or r.stdout or '').strip()
return jsonify({
'error': 'proxmox-backup-client did not recognise this file as a valid PBS keyfile',
'tool_output': tool_output[:800] if tool_output else '(no output from proxmox-backup-client key info)',
'tool_exit_code': r.returncode,
}), 400
# No content validation. The operator brings their own key —
# any KDF, any format, any origin. If it later doesn't work
# with proxmox-backup-client the failure will surface at
# backup time with a clear error. This is a deliberate
# trust-the-operator design: keeping `key info` validation
# here rejected legitimate scrypt-encoded keyfiles that could
# only be inspected with their original passphrase, which is
# exactly the case ProxMenux can't provide in a non-interactive
# flow.
# The old recovery blob was encrypted with the old key — a new
# blob has to be built by re-running /pbs-recovery/setup once
# the operator confirms this import.
try:
os.remove(_PBS_RECOVERY_ENC_PATH)
except FileNotFoundError:
pass
except OSError:
pass
for stale in (_PBS_RECOVERY_ENC_PATH, _PBS_KEYFILE_PASS_PATH):
try:
os.remove(stale)
except FileNotFoundError:
pass
except OSError:
pass
try:
os.replace(tmp_path, _PBS_KEYFILE_PATH)
@@ -14095,9 +14105,22 @@ def api_pbs_encryption_import():
except OSError as e:
return jsonify({'error': f'cannot promote keyfile: {e}'}), 500
# Persist the keyfile passphrase alongside the key, chmod 600.
# Same trust boundary as the keyfile itself: root-only. Empty
# value → don't write anything (blank passphrase == kdf=none).
if keyfile_pass:
try:
fd = os.open(_PBS_KEYFILE_PASS_PATH,
os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, 'w') as f:
f.write(keyfile_pass)
except OSError as e:
return jsonify({'error': f'cannot store keyfile passphrase: {e}'}), 500
return jsonify({
'status': 'ok',
'keyfile_path': _PBS_KEYFILE_PATH,
'keyfile_pass_stored': bool(keyfile_pass),
'recovery_reset': True,
})
finally:

View File

@@ -1239,33 +1239,54 @@ _hb_pbs_import_dialog() {
# 1. Ask the path.
src=$(dialog --backtitle "ProxMenux" --title "$(hb_translate "Import PBS keyfile")" \
--inputbox "$(hb_translate "Absolute path to your existing PBS keyfile:")"$'\n\n'"$(hb_translate "The file is validated with 'proxmox-backup-client key info' and copied to")"$'\n'"$key_file $(hb_translate "with chmod 600.")" \
--inputbox "$(hb_translate "Absolute path to your existing PBS keyfile:")"$'\n\n'"$(hb_translate "The file is copied to")"$'\n'"$key_file $(hb_translate "with chmod 600. ProxMenux does not validate the contents — any keyfile you accept as valid on your PBS is accepted here.")" \
14 78 "" 3>&1 1>&2 2>&3) || return 1
src="$(echo "$src" | xargs)"
[[ -z "$src" ]] && return 1
# 2. Validate the source without touching the canonical path yet.
# Same shape as the check inside hb_pbs_import_keyfile, so a
# later install call can't disagree with what we saw here.
# 2. Basic existence + readability. We deliberately do NOT validate
# the content: `proxmox-backup-client key info` refuses scrypt
# keyfiles that require their original passphrase, and forcing
# the operator to strip encryption before importing is worse
# than trusting their input. If the file is not a real keyfile,
# the first backup will fail with a clear message.
if [[ ! -s "$src" || ! -r "$src" ]]; then
dialog --backtitle "ProxMenux" --title "$(hb_translate "Import failed")" \
--msgbox "$(hb_translate "The file does not exist, is empty or is not readable.")"$'\n\n'"$(hb_translate "Path:") $src" \
10 78
return 1
fi
if ! proxmox-backup-client key info --output-format json "$src" >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(hb_translate "Import failed")" \
--msgbox "$(hb_translate "proxmox-backup-client did not recognise this file as a valid PBS keyfile.")"$'\n\n'"$(hb_translate "Path:") $src" \
10 78
return 1
fi
# 3. Collect the recovery passphrase. Cancel here → zero side effect
# 3. Ask for the keyfile passphrase. Blank == kdf=none (no
# passphrase). If the source key uses --kdf scrypt the operator
# types it here and we persist it next to the keyfile so the
# scheduled runner (systemd, no stdin) can unlock the key
# without prompting.
local keyfile_pass keyfile_pass2
while true; do
keyfile_pass=$(dialog --backtitle "ProxMenux" --title "$(hb_translate "Keyfile passphrase")" \
--insecure --passwordbox "$(hb_translate "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):")" \
"$HB_UI_PASS_H" "$HB_UI_PASS_W" "" 3>&1 1>&2 2>&3) || return 1
# Blank is a legitimate answer — kdf=none, no passphrase.
if [[ -z "$keyfile_pass" ]]; then
keyfile_pass2=""
break
fi
keyfile_pass2=$(dialog --backtitle "ProxMenux" --title "$(hb_translate "Keyfile passphrase")" \
--insecure --passwordbox "$(hb_translate "Confirm the keyfile passphrase:")" \
"$HB_UI_PASS_H" "$HB_UI_PASS_W" "" 3>&1 1>&2 2>&3) || return 1
[[ "$keyfile_pass" == "$keyfile_pass2" ]] && break
dialog --backtitle "ProxMenux" \
--msgbox "$(hb_translate "Passphrases do not match. Try again.")" 8 50
done
# 4. Collect the recovery passphrase. Cancel here → zero side effect
# (the source file is still where it was; we haven't copied yet).
local pass
pass=$(_hb_pbs_prompt_recovery_pass) || return 1
# 4. Now install the keyfile. Point of no return.
# 5. Install the keyfile. Point of no return.
# hb_pbs_import_keyfile just copies + chmods; content is not inspected.
if ! hb_pbs_import_keyfile "$src"; then
dialog --backtitle "ProxMenux" --title "$(hb_translate "Import failed")" \
--msgbox "$(hb_translate "Could not copy the keyfile into place. Check permissions on:") $HB_STATE_DIR" \
@@ -1274,10 +1295,27 @@ _hb_pbs_import_dialog() {
fi
msg_ok "$(hb_translate "Imported existing encryption key:") $key_file"
# 5. Encrypt the recovery blob with the passphrase collected in step 3.
# 6. Persist the keyfile passphrase (if any) next to the keyfile.
# Same trust boundary — chmod 600, root-only. Empty passphrase
# means no file: the runner treats absent file as kdf=none.
# Also export HB_PBS_ENC_PASS so _bk_pbs (called right after
# this returns) picks it up for the current backup without
# needing to re-read the file.
local keyfile_pass_file="$HB_STATE_DIR/pbs-key.pass"
if [[ -n "$keyfile_pass" ]]; then
umask 077
printf '%s' "$keyfile_pass" > "$keyfile_pass_file"
chmod 600 "$keyfile_pass_file" 2>/dev/null || true
HB_PBS_ENC_PASS="$keyfile_pass"
else
rm -f "$keyfile_pass_file" 2>/dev/null || true
HB_PBS_ENC_PASS=""
fi
# 7. Encrypt the recovery blob with the recovery passphrase.
# openssl failure is the only path that has to undo the install.
if ! _hb_pbs_finalize_recovery "$pass"; then
rm -f "$key_file" "$recovery_enc" 2>/dev/null
rm -f "$key_file" "$recovery_enc" "$keyfile_pass_file" 2>/dev/null
HB_PBS_KEYFILE_OPT=""
return 1
fi
@@ -1324,6 +1362,13 @@ hb_ask_pbs_encryption() {
# ── Step 2a: keyfile already present → reuse silently ─────
if [[ -s "$key_file" ]]; then
HB_PBS_KEYFILE_OPT="--keyfile $key_file"
# Load the stored keyfile passphrase if any — imports of
# scrypt-encoded keyfiles persist it here so proxmox-backup-client
# can unlock the key without a stdin prompt.
local _pass_file="$HB_STATE_DIR/pbs-key.pass"
if [[ -s "$_pass_file" ]]; then
HB_PBS_ENC_PASS=$(cat "$_pass_file" 2>/dev/null || true)
fi
msg_ok "$(hb_translate "Using existing encryption key:") $key_file"
return 0
fi

View File

@@ -376,6 +376,20 @@ _sb_run_pbs() {
PBS_FINGERPRINT=$(_sb_pbs_resolve_fingerprint "$PBS_REPOSITORY" 2>/dev/null || true)
fi
# PBS_ENCRYPTION_PASSWORD: the passphrase that unlocks the keyfile
# itself when it was created with `--kdf scrypt`. The Import flow
# persists it at pbs-key.pass (chmod 600, root-only, same trust
# boundary as the keyfile). If the file is absent the keyfile is
# kdf=none and no passphrase is needed. `PBS_ENCRYPTION_PASSWORD`
# from the .env still wins if the operator set it there for a
# per-job override.
if [[ -z "${PBS_ENCRYPTION_PASSWORD:-}" ]]; then
local _pbs_pass_file="/usr/local/share/proxmenux/pbs-key.pass"
if [[ -r "$_pbs_pass_file" ]]; then
PBS_ENCRYPTION_PASSWORD=$(cat "$_pbs_pass_file" 2>/dev/null || true)
fi
fi
[[ -z "${PBS_REPOSITORY:-}" || -z "${PBS_PASSWORD:-}" ]] && return 1
if [[ -n "${PBS_KEYFILE:-}" ]]; then
cmd+=(--keyfile "$PBS_KEYFILE")

View File

@@ -52,7 +52,7 @@
"heading": "Client-side encryption",
"intro": "PBS client-side keyfile encryption encrypts chunks on the source host before upload. ProxMenux enables the feature with one added constraint: a recovery passphrase is mandatory when encryption is enabled. The passphrase does not protect the local keyfile; it protects the escrow copy of the keyfile that ProxMenux uploads to PBS for disaster recovery.",
"keyfileTitle": "Keyfile",
"keyfileBody": "The encryption prompt is a two-step yes/no. Step one asks whether to encrypt the backup: <em>No</em> continues without encryption; <em>Yes</em> moves to step two. Step two depends on whether a keyfile is already installed at <code>/usr/local/share/proxmenux/pbs-key.conf</code>. If it is, the installed keyfile is reused silently and the backup proceeds. If it is not, a two-option menu asks how to set one up: <em>Generate a new keyfile</em> runs <code>proxmox-backup-client key create --kdf none</code>, or <em>Import an existing keyfile</em> takes a path the operator supplies, validates it with <code>proxmox-backup-client key info</code> and installs it at the canonical path. Both branches run only after the recovery passphrase has been confirmed — cancelling any dialog before that point leaves the disk unchanged.",
"keyfileBody": "The encryption prompt is a two-step yes/no. Step one asks whether to encrypt the backup: <em>No</em> continues without encryption; <em>Yes</em> moves to step two. Step two depends on whether a keyfile is already installed at <code>/usr/local/share/proxmenux/pbs-key.conf</code>. If it is, the installed keyfile is reused silently and the backup proceeds. If it is not, a two-option menu asks how to set one up. <em>Generate a new keyfile</em> runs <code>proxmox-backup-client key create --kdf none</code> — the resulting keyfile has no passphrase, so the scheduled runner can use it under systemd without any interactive prompt. <em>Import an existing keyfile</em> takes a path the operator supplies and copies it into place with <code>chmod 600</code>; ProxMenux does not inspect the contents (any keyfile the operator accepts as valid on their PBS is accepted here, including scrypt-encoded keyfiles that could not be validated non-interactively). When importing, the flow also asks for the keyfile's own passphrase — the one that <code>proxmox-backup-client key create --kdf scrypt</code> asked for at creation. That passphrase is persisted at <code>/usr/local/share/proxmenux/pbs-key.pass</code> (chmod 600) and reused by every encrypted job on this host as <code>PBS_ENCRYPTION_PASSWORD</code>. Leaving it blank means the keyfile is <code>--kdf none</code> (no passphrase). Both branches run only after the recovery passphrase has been confirmed — cancelling any dialog before that point leaves the disk unchanged.",
"modesTitle": "Per-host or shared keyfile",
"modesIntro": "Both operating models are supported and neither is enforced — the choice belongs to the operator based on how their fleet is organised.",
"modesPerHostTitle": "Per-host keyfile (default)",

View File

@@ -52,7 +52,7 @@
"heading": "Cifrado del lado del cliente",
"intro": "El cifrado por keyfile del lado del cliente de PBS cifra los chunks en el host de origen antes de la subida. ProxMenux habilita esta funcionalidad con una restricción añadida: la passphrase de recuperación es obligatoria cuando se activa el cifrado. La passphrase no protege el keyfile local; protege la copia de escrow del keyfile que ProxMenux sube a PBS para recuperación ante desastre.",
"keyfileTitle": "Keyfile",
"keyfileBody": "El diálogo de cifrado sigue un flujo yes/no en dos pasos. El primer paso pregunta si se desea cifrar el backup: <em>No</em> continúa sin cifrado; <em>Yes</em> pasa al segundo paso. El segundo paso depende de si ya hay un keyfile instalado en <code>/usr/local/share/proxmenux/pbs-key.conf</code>. Si lo hay, se reutiliza silenciosamente y el backup continúa. Si no lo hay, aparece un menú de dos opciones: <em>Generate a new keyfile</em>, que ejecuta <code>proxmox-backup-client key create --kdf none</code>, o <em>Import an existing keyfile</em>, que toma una ruta indicada por el operador, la valida con <code>proxmox-backup-client key info</code> y la instala en la ruta canónica. Ambas ramas se ejecutan solo tras confirmar la passphrase de recuperación — cancelar cualquier diálogo antes de ese punto deja el disco intacto.",
"keyfileBody": "El diálogo de cifrado sigue un flujo yes/no en dos pasos. El primer paso pregunta si se desea cifrar el backup: <em>No</em> continúa sin cifrado; <em>Yes</em> pasa al segundo paso. El segundo paso depende de si ya hay un keyfile instalado en <code>/usr/local/share/proxmenux/pbs-key.conf</code>. Si lo hay, se reutiliza silenciosamente y el backup continúa. Si no lo hay, aparece un menú de dos opciones. <em>Generate a new keyfile</em> ejecuta <code>proxmox-backup-client key create --kdf none</code> — el keyfile resultante no lleva passphrase, así que el runner programado bajo systemd puede usarlo sin prompt interactivo. <em>Import an existing keyfile</em> toma una ruta indicada por el operador y la copia en la ubicación canónica con <code>chmod 600</code>; ProxMenux no inspecciona el contenido (cualquier keyfile que el operador acepte como válido en su PBS se acepta aquí, incluyendo keyfiles cifrados con scrypt que no podrían validarse de forma no interactiva). Al importar, el flujo pide también la passphrase del propio keyfile — la que <code>proxmox-backup-client key create --kdf scrypt</code> pidió al crearlo. Esa passphrase se persiste en <code>/usr/local/share/proxmenux/pbs-key.pass</code> (chmod 600) y se reutiliza en cada job cifrado del host como <code>PBS_ENCRYPTION_PASSWORD</code>. Dejarla en blanco equivale a un keyfile <code>--kdf none</code> (sin passphrase). Ambas ramas se ejecutan solo tras confirmar la passphrase de recuperación — cancelar cualquier diálogo antes de ese punto deja el disco intacto.",
"modesTitle": "Keyfile por host o compartido",
"modesIntro": "Ambos modelos operativos están soportados y ninguno se impone — la decisión pertenece al usuario según cómo esté organizada su flota.",
"modesPerHostTitle": "Keyfile por host (por defecto)",