mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
docs: review backup and restore documentation in all seven translations
This commit is contained in:
@@ -20,9 +20,21 @@
|
||||
"heading": "The direction check",
|
||||
"intro": "During the compatibility check, <code>hb_compat_check</code> compares the kernel recorded in the backup's manifest against the target's current kernel (<code>uname -r</code>) and classifies the restore into one of three cases, stored in the internal variable <code>HB_COMPAT_KERNEL_DIRECTION</code>:",
|
||||
"rows": [
|
||||
{ "direction": "same", "condition": "The major kernel version matches between backup and target.", "behavior": "The full restore flow runs unchanged. No extra filter, no hydration, no special notice in the UI." },
|
||||
{ "direction": "bk_newer", "condition": "Backup kernel is NEWER than the target's (for example: the backup was made on kernel 7.0 and the target runs kernel 6.17).", "behavior": "The full restore flow runs unchanged, exactly like <code>same</code>. No filter is applied. Verified empirically: drivers auto-reinstall against the target's kernel, IOMMU config applies cleanly, VMs with GPU passthrough come up without issue." },
|
||||
{ "direction": "bk_older", "condition": "Backup kernel is OLDER than the target's (for example: the backup was made on kernel 6.17 and the target runs kernel 7.0).", "behavior": "The safe-subset filter and the four-phase hydration below are activated. The restore proceeds — the target reproduces the source, but boot-critical files are not copied verbatim." }
|
||||
{
|
||||
"direction": "same",
|
||||
"condition": "The major kernel version matches between backup and target.",
|
||||
"behavior": "The full restore flow runs unchanged. No extra filter, no hydration, no special notice in the UI."
|
||||
},
|
||||
{
|
||||
"direction": "bk_newer",
|
||||
"condition": "Backup kernel is NEWER than the target's (for example: the backup was made on kernel 7.0 and the target runs kernel 6.17).",
|
||||
"behavior": "The full restore flow runs unchanged, exactly like <code>same</code>. No filter is applied. Verified empirically: drivers auto-reinstall against the target's kernel, IOMMU config applies cleanly, VMs with GPU passthrough come up without issue."
|
||||
},
|
||||
{
|
||||
"direction": "bk_older",
|
||||
"condition": "Backup kernel is OLDER than the target's (for example: the backup was made on kernel 6.17 and the target runs kernel 7.0).",
|
||||
"behavior": "The safe-subset filter and the four-phase hydration below are activated. The restore proceeds — the target reproduces the source, but boot-critical files are not copied verbatim."
|
||||
}
|
||||
]
|
||||
},
|
||||
"whyBkNewerIsSafe": {
|
||||
@@ -31,25 +43,60 @@
|
||||
},
|
||||
"safeSubsetFilter": {
|
||||
"heading": "The safe-subset filter (only when the target kernel is newer)",
|
||||
"intro": "When the target kernel is newer than the backup's, the compatibility check adds 16 boot-critical paths from <code>hb_unsafe_paths_cross_version</code> to <code>RS_SKIP_PATHS</code>. These paths are excluded from the restore because writing a version of them from an older kernel over a target running a newer one has caused kernel panics in bench tests. The paths cover four categories:",
|
||||
"intro": "When the target kernel is newer than the backup's, the compatibility check adds 16 boot-critical paths from <code>hb_unsafe_paths_cross_version</code> to <code>RS_SKIP_PATHS</code>. These paths are excluded from the restore because writing a version of them from an older kernel over a target running a newer one has caused kernel panics in bench tests. The paths cover five categories:",
|
||||
"categoryRows": [
|
||||
{ "category": "Bootloader", "paths": "<code>/etc/default/grub</code>, <code>/etc/kernel</code>", "reason": "GRUB defaults tied to the prior kernel order, and proxmox-boot-tool state (cmdline, ESP UUIDs, hooks) that references paths and identifiers from the older install." },
|
||||
{ "category": "Kernel modules and boot artifacts", "paths": "<code>/etc/modules-load.d</code>, <code>/etc/modprobe.d</code>, <code>/etc/initramfs-tools</code>", "reason": "Autoload lists that may reference modules renamed between kernel majors, module options that may not apply, initramfs hooks written for the older kernel." },
|
||||
{ "category": "Storage stack and filesystem identity", "paths": "<code>/etc/fstab</code>, <code>/etc/multipath</code>, <code>/etc/iscsi</code>, <code>/etc/udev/rules.d</code>, <code>/etc/zfs</code>", "reason": "UUIDs that may not exist on this install, multipath drivers that change between kernels, iSCSI parameters that evolve, udev rules that may bind to nonexistent subsystems, ZFS state (<code>zpool.cache</code> + <code>hostid</code>) that can lock the pool as foreign." },
|
||||
{ "category": "APT sources", "paths": "<code>/etc/apt</code>", "reason": "APT source suites may trigger a downgrade of critical packages on the next upgrade." },
|
||||
{ "category": "systemd", "paths": "<code>/etc/systemd/system</code>, <code>/etc/systemd/journald.conf</code>, <code>/etc/systemd/logind.conf</code>, <code>/etc/systemd/system.conf</code>, <code>/etc/systemd/user.conf</code>", "reason": "Unit overrides and .wants tied to the older systemd major; configuration keys that may not parse on a newer systemd." }
|
||||
{
|
||||
"category": "Bootloader",
|
||||
"paths": "<code>/etc/default/grub</code>, <code>/etc/kernel</code>",
|
||||
"reason": "GRUB defaults tied to the prior kernel order, and proxmox-boot-tool state (cmdline, ESP UUIDs, hooks) that references paths and identifiers from the older install."
|
||||
},
|
||||
{
|
||||
"category": "Kernel modules and boot artifacts",
|
||||
"paths": "<code>/etc/modules-load.d</code>, <code>/etc/modprobe.d</code>, <code>/etc/initramfs-tools</code>",
|
||||
"reason": "Autoload lists that may reference modules renamed between kernel majors, module options that may not apply, initramfs hooks written for the older kernel."
|
||||
},
|
||||
{
|
||||
"category": "Storage stack and filesystem identity",
|
||||
"paths": "<code>/etc/fstab</code>, <code>/etc/multipath</code>, <code>/etc/iscsi</code>, <code>/etc/udev/rules.d</code>, <code>/etc/zfs</code>",
|
||||
"reason": "UUIDs that may not exist on this install, multipath drivers that change between kernels, iSCSI parameters that evolve, udev rules that may bind to nonexistent subsystems, ZFS state (<code>zpool.cache</code> + <code>hostid</code>) that can lock the pool as foreign."
|
||||
},
|
||||
{
|
||||
"category": "APT sources",
|
||||
"paths": "<code>/etc/apt</code>",
|
||||
"reason": "APT source suites may trigger a downgrade of critical packages on the next upgrade."
|
||||
},
|
||||
{
|
||||
"category": "systemd",
|
||||
"paths": "<code>/etc/systemd/system</code>, <code>/etc/systemd/journald.conf</code>, <code>/etc/systemd/logind.conf</code>, <code>/etc/systemd/system.conf</code>, <code>/etc/systemd/user.conf</code>",
|
||||
"reason": "Unit overrides and .wants tied to the older systemd major; configuration keys that may not parse on a newer systemd."
|
||||
}
|
||||
],
|
||||
"outroBody": "The filter runs BEFORE the confirmation dialog so the user sees the exact list of paths that will be skipped, categorised by reason. The restore still proceeds — everything else (VMs, LXCs, network, /etc/pve, users, cron, ProxMenux state, packages, drivers, /root) is restored normally."
|
||||
"outroBody": "The filter runs BEFORE the confirmation dialog so the user sees the exact list of paths that will be skipped, categorised by reason. The restore still proceeds — everything else (VM/LXC configuration, network, /etc/pve, users, cron, ProxMenux state, packages, drivers, /root) is restored normally."
|
||||
},
|
||||
"hydration": {
|
||||
"heading": "Kernel-agnostic hydration",
|
||||
"intro": "The safe-subset filter alone would leave the target without the tuning the user had inside those boot-critical files: IOMMU cmdline for GPU passthrough, VFIO device IDs, custom <code>GRUB_TIMEOUT</code>, nvidia blacklists. The hydration pass re-applies those bits kernel-agnostically. Four phases run when the target kernel is newer than the backup's, each additive (never overwrites a value the target already carries) and idempotent (running twice is a no-op).",
|
||||
"intro": "The filter omits boot-critical files, so hydration reapplies selected user tuning: IOMMU cmdline, VFIO IDs, custom <code>GRUB_TIMEOUT</code> and nvidia blacklists. It runs when the target kernel is newer. Cmdline merges preserve existing target keys and module merges append missing entries. Whitelisted GRUB values and user files may replace differing target content. Repeating the same input is idempotent.",
|
||||
"phaseRows": [
|
||||
{ "phase": "1a — GRUB path", "detail": "For hosts using GRUB (ext4/lvm installs). <code>_rs_hyd_grub</code> merges every token from the backup's <code>manifest.kernel_params.cmdline_extra</code> into the target's live <code>GRUB_CMDLINE_LINUX_DEFAULT</code>, skipping tokens whose key the target already carries. Then merges whitelisted <code>GRUB_*</code> keys (<code>GRUB_TIMEOUT</code>, <code>GRUB_TIMEOUT_STYLE</code>, <code>GRUB_DEFAULT</code>, <code>GRUB_TERMINAL</code>, <code>GRUB_DISABLE_OS_PROBER</code>, <code>GRUB_SERIAL_COMMAND</code>, <code>GRUB_GFXMODE</code>, <code>GRUB_GFXPAYLOAD_LINUX</code>) from the backup's <code>/etc/default/grub</code> if they differ from the target's." },
|
||||
{ "phase": "1b — systemd-boot / ZFS path", "detail": "For hosts using systemd-boot (typically ZFS-on-root). <code>_rs_hyd_kernel_cmdline</code> merges user tokens from <code>cmdline_extra</code> into the target's <code>/etc/kernel/cmdline</code>, keeping the target's own <code>root=</code>, <code>boot=</code> and <code>rootflags=</code> boilerplate intact." },
|
||||
{ "phase": "2 — /etc/modules merge", "detail": "<code>_rs_hyd_modules</code> appends modules from <code>manifest.kernel_params.modules_loaded_at_boot</code> that are in the whitelist (<code>vfio</code>, <code>vfio_pci</code>, <code>vfio_iommu_type1</code>, <code>vfio_virqfd</code>, <code>kvm</code>, <code>kvm_intel</code>, <code>kvm_amd</code>, <code>nvidia</code>, <code>nvidia_drm</code>, <code>nvidia_modeset</code>, <code>nvidia_uvm</code>, <code>i915</code>, <code>xe</code>) AND not already present in the target's <code>/etc/modules</code>." },
|
||||
{ "phase": "3 — Whitelisted files copy", "detail": "<code>_rs_hyd_files</code> copies user-authored files from the staging rootfs to the live target when the content differs. Whitelist covers VFIO/nvidia/blacklist files under <code>/etc/modprobe.d</code>, <code>/etc/modules-load.d</code>, and the ProxMenux VFIO bind rule + nvidia udev rules under <code>/etc/udev/rules.d</code>. Distro-owned files (<code>pve-blacklist.conf</code>, <code>mdadm.conf</code>, <code>nvme.conf</code>) are intentionally excluded — their contents evolve between releases." },
|
||||
{ "phase": "4 — Force post-boot reflows", "detail": "The four phases write directly to the live target OUTSIDE the normal restore pipeline. To make the merged tokens/modules/files take effect on the next boot, <code>HB_HYDRATION_APPLIED=1</code> propagates through <code>plan.env</code> to <code>apply_pending_restore.sh</code>, which forces <code>NEEDS_INITRAMFS=1</code> and <code>NEEDS_GRUB=1</code> regardless of what was in the apply list. The post-boot dispatcher then regenerates initramfs and refreshes the bootloader." }
|
||||
{
|
||||
"phase": "1a — GRUB path",
|
||||
"detail": "For hosts using GRUB (ext4/lvm installs). <code>_rs_hyd_grub</code> merges every token from the backup's <code>manifest.kernel_params.cmdline_extra</code> into the target's live <code>GRUB_CMDLINE_LINUX_DEFAULT</code>, skipping tokens whose key the target already carries. Then merges whitelisted <code>GRUB_*</code> keys (<code>GRUB_TIMEOUT</code>, <code>GRUB_TIMEOUT_STYLE</code>, <code>GRUB_DEFAULT</code>, <code>GRUB_TERMINAL</code>, <code>GRUB_DISABLE_OS_PROBER</code>, <code>GRUB_SERIAL_COMMAND</code>, <code>GRUB_GFXMODE</code>, <code>GRUB_GFXPAYLOAD_LINUX</code>) from the backup's <code>/etc/default/grub</code> if they differ from the target's."
|
||||
},
|
||||
{
|
||||
"phase": "1b — systemd-boot / ZFS path",
|
||||
"detail": "For hosts using systemd-boot (typically ZFS-on-root). <code>_rs_hyd_kernel_cmdline</code> merges user tokens from <code>cmdline_extra</code> into the target's <code>/etc/kernel/cmdline</code>, keeping the target's own <code>root=</code>, <code>boot=</code> and <code>rootflags=</code> boilerplate intact."
|
||||
},
|
||||
{
|
||||
"phase": "2 — /etc/modules merge",
|
||||
"detail": "<code>_rs_hyd_modules</code> appends modules from <code>manifest.kernel_params.modules_loaded_at_boot</code> that are in the whitelist (<code>vfio</code>, <code>vfio_pci</code>, <code>vfio_iommu_type1</code>, <code>vfio_virqfd</code>, <code>kvm</code>, <code>kvm_intel</code>, <code>kvm_amd</code>, <code>nvidia</code>, <code>nvidia_drm</code>, <code>nvidia_modeset</code>, <code>nvidia_uvm</code>, <code>i915</code>, <code>xe</code>) AND not already present in the target's <code>/etc/modules</code>."
|
||||
},
|
||||
{
|
||||
"phase": "3 — Whitelisted files copy",
|
||||
"detail": "<code>_rs_hyd_files</code> copies user-authored files from the staging rootfs to the live target when the content differs. Whitelist covers VFIO/nvidia/blacklist files under <code>/etc/modprobe.d</code>, <code>/etc/modules-load.d</code>, and the ProxMenux VFIO bind rule + nvidia udev rules under <code>/etc/udev/rules.d</code>. Distro-owned files (<code>pve-blacklist.conf</code>, <code>mdadm.conf</code>, <code>nvme.conf</code>) are intentionally excluded — their contents evolve between releases."
|
||||
},
|
||||
{
|
||||
"phase": "4 — Force post-boot reflows",
|
||||
"detail": "The four phases write directly to the live target OUTSIDE the normal restore pipeline. To make the merged tokens/modules/files take effect on the next boot, <code>HB_HYDRATION_APPLIED=1</code> propagates through <code>plan.env</code> to <code>apply_pending_restore.sh</code>, which forces <code>NEEDS_INITRAMFS=1</code> and <code>NEEDS_GRUB=1</code> regardless of what was in the apply list. The post-boot dispatcher then regenerates initramfs and refreshes the bootloader."
|
||||
}
|
||||
]
|
||||
},
|
||||
"planCommit": {
|
||||
@@ -65,8 +112,14 @@
|
||||
"heading": "Concrete examples",
|
||||
"intro": "The hydration pass is not abstract — it produces observable, correct outcomes for common scenarios. Two examples that hydration handles automatically:",
|
||||
"rows": [
|
||||
{ "scenario": "GPU passthrough (VFIO)", "detail": "Source had <code>intel_iommu=on iommu=pt</code> in the cmdline, <code>vfio</code>/<code>vfio_pci</code>/<code>vfio_iommu_type1</code> in <code>/etc/modules</code>, an <code>/etc/modprobe.d/vfio.conf</code> with <code>options vfio-pci ids=10de:2216</code>, and a <code>/etc/modprobe.d/blacklist-nvidia.conf</code>. Hydration merges the cmdline tokens into the target's GRUB or kernel cmdline, appends the vfio modules to <code>/etc/modules</code>, and copies the two user-authored modprobe files. On the next boot, IOMMU is active, VFIO modules load, the GPU is bound to vfio-pci and the VM comes up with passthrough working." },
|
||||
{ "scenario": "Custom GRUB defaults", "detail": "Source had <code>GRUB_TIMEOUT=1</code> and <code>GRUB_DISABLE_OS_PROBER=true</code>. Hydration reads both keys from the backup's <code>/etc/default/grub</code>, sees they differ from the fresh install's defaults, and rewrites those two lines in the target's file (leaving everything else, including <code>GRUB_DISTRIBUTOR</code>, untouched)." }
|
||||
{
|
||||
"scenario": "GPU passthrough (VFIO)",
|
||||
"detail": "Source had <code>intel_iommu=on iommu=pt</code> in the cmdline, <code>vfio</code>/<code>vfio_pci</code>/<code>vfio_iommu_type1</code> in <code>/etc/modules</code>, an <code>/etc/modprobe.d/vfio.conf</code> with <code>options vfio-pci ids=10de:2216</code>, and a <code>/etc/modprobe.d/blacklist-nvidia.conf</code>. Hydration merges the cmdline tokens into the target's GRUB or kernel cmdline, appends the vfio modules to <code>/etc/modules</code>, and copies the two user-authored modprobe files. On the next boot, IOMMU is active, VFIO modules load, the GPU is bound to vfio-pci and the VM comes up with passthrough working."
|
||||
},
|
||||
{
|
||||
"scenario": "Custom GRUB defaults",
|
||||
"detail": "Source had <code>GRUB_TIMEOUT=1</code> and <code>GRUB_DISABLE_OS_PROBER=true</code>. Hydration reads both keys from the backup's <code>/etc/default/grub</code>, sees they differ from the fresh install's defaults, and rewrites those two lines in the target's file (leaving everything else, including <code>GRUB_DISTRIBUTOR</code>, untouched)."
|
||||
}
|
||||
]
|
||||
},
|
||||
"callout": {
|
||||
@@ -77,18 +130,41 @@
|
||||
"heading": "Where the mechanisms live",
|
||||
"intro": "For developers looking to trace or extend the cross-kernel behaviour:",
|
||||
"rows": [
|
||||
{ "component": "Direction detection", "location": "<code>hb_compat_check</code> in <code>lib_host_backup_common.sh</code>. Sets <code>HB_COMPAT_KERNEL_DIRECTION</code>." },
|
||||
{ "component": "Safe-subset path list", "location": "<code>hb_unsafe_paths_cross_version</code> in <code>lib_host_backup_common.sh</code>. Emits <code>path\\treason</code> lines used by both the CLI filter and the Web endpoint <code>/api/host-backups/restore/prepare</code>." },
|
||||
{ "component": "Hydration phases", "location": "<code>_rs_hyd_grub</code>, <code>_rs_hyd_kernel_cmdline</code>, <code>_rs_hyd_modules</code>, <code>_rs_hyd_files</code> in <code>backup_host.sh</code>. Orchestrated by <code>_rs_apply_bk_older_hydration</code>." },
|
||||
{ "component": "Post-boot reflow forcing", "location": "<code>apply_pending_restore.sh</code> reads <code>HB_HYDRATION_APPLIED</code> from <code>plan.env</code> and forces <code>NEEDS_INITRAMFS=1</code>/<code>NEEDS_GRUB=1</code>." },
|
||||
{ "component": "Web preview", "location": "<code>/api/host-backups/restore/prepare</code> in <code>flask_server.py</code>. Sources the helper library, runs <code>_rs_apply_bk_older_hydration</code> in plan mode, returns the actions to the Web modal." }
|
||||
{
|
||||
"component": "Direction detection",
|
||||
"location": "<code>hb_compat_check</code> in <code>lib_host_backup_common.sh</code>. Sets <code>HB_COMPAT_KERNEL_DIRECTION</code>."
|
||||
},
|
||||
{
|
||||
"component": "Safe-subset path list",
|
||||
"location": "<code>hb_unsafe_paths_cross_version</code> in <code>lib_host_backup_common.sh</code>. Emits <code>path\\treason</code> lines used by both the CLI filter and the Web endpoint <code>/api/host-backups/restore/prepare</code>."
|
||||
},
|
||||
{
|
||||
"component": "Hydration phases",
|
||||
"location": "<code>_rs_hyd_grub</code>, <code>_rs_hyd_kernel_cmdline</code>, <code>_rs_hyd_modules</code>, <code>_rs_hyd_files</code> in <code>backup_host.sh</code>. Orchestrated by <code>_rs_apply_bk_older_hydration</code>."
|
||||
},
|
||||
{
|
||||
"component": "Post-boot reflow forcing",
|
||||
"location": "<code>apply_pending_restore.sh</code> reads <code>HB_HYDRATION_APPLIED</code> from <code>plan.env</code> and forces <code>NEEDS_INITRAMFS=1</code>/<code>NEEDS_GRUB=1</code>."
|
||||
},
|
||||
{
|
||||
"component": "Web preview",
|
||||
"location": "<code>/api/host-backups/restore/prepare</code> in <code>flask_server.py</code>. Sources the helper library, runs <code>_rs_apply_bk_older_hydration</code> in plan mode, returns the actions to the Web modal."
|
||||
}
|
||||
]
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Where to go next",
|
||||
"items": [
|
||||
{ "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the full restore pipeline this page's mechanisms plug into." },
|
||||
{ "label": "How it works", "href": "/docs/backup-restore/how-it-works", "tail": " — the manifest and collectors that produce the <code>kernel_params</code> block hydration reads from." }
|
||||
{
|
||||
"label": "Restoring",
|
||||
"href": "/docs/backup-restore/restoring",
|
||||
"tail": " — the full restore pipeline this page's mechanisms plug into."
|
||||
},
|
||||
{
|
||||
"label": "How it works",
|
||||
"href": "/docs/backup-restore/how-it-works",
|
||||
"tail": " — the manifest and collectors that produce the <code>kernel_params</code> block hydration reads from."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,19 +24,47 @@
|
||||
"heading": "The borg binary",
|
||||
"intro": "Borg is not a base dependency of the ProxMenux installer. When a backup runs, <code>hb_ensure_borg</code> resolves the binary from four sources in order and returns the first that works:",
|
||||
"rows": [
|
||||
{ "priority": "1", "source": "System <code>borg</code>", "detail": "If the host already has <code>borg</code> installed via APT (<code>which borg</code> resolves), that binary is used." },
|
||||
{ "priority": "2", "source": "State-dir cache", "detail": "<code>/usr/local/share/proxmenux/borg</code> — kept from a prior GitHub download on this host." },
|
||||
{ "priority": "3", "source": "Monitor AppImage bundle", "detail": "<code>/usr/local/share/proxmenux/monitor-app/usr/bin/borg</code> — the AppImage bundles a signed borg-linux64. This is the offline-safe path: a host with no internet still has a working <code>borg</code>." },
|
||||
{ "priority": "4", "source": "GitHub download", "detail": "<code>wget</code> against the pinned <code>borg-linux64</code> URL under <code>github.com/borgbackup/borg/releases/</code>, verified against a constant SHA-256 in <code>lib_host_backup_common.sh</code> (<code>HB_BORG_LINUX64_SHA256</code>). On checksum mismatch the binary is discarded and the backup aborts. The downloaded file is cached in the state dir so subsequent backups use path 2." }
|
||||
{
|
||||
"priority": "1",
|
||||
"source": "System <code>borg</code>",
|
||||
"detail": "If the host already has <code>borg</code> installed via APT (<code>which borg</code> resolves), that binary is used."
|
||||
},
|
||||
{
|
||||
"priority": "2",
|
||||
"source": "State-dir cache",
|
||||
"detail": "<code>/usr/local/share/proxmenux/borg</code> — kept from a prior GitHub download on this host."
|
||||
},
|
||||
{
|
||||
"priority": "3",
|
||||
"source": "Monitor AppImage bundle",
|
||||
"detail": "<code>/usr/local/share/proxmenux/monitor-app/usr/bin/borg</code> — the AppImage bundles a signed borg-linux64. This is the offline-safe path: a host with no internet still has a working <code>borg</code>."
|
||||
},
|
||||
{
|
||||
"priority": "4",
|
||||
"source": "GitHub download",
|
||||
"detail": "<code>wget</code> against the pinned <code>borg-linux64</code> URL under <code>github.com/borgbackup/borg/releases/</code>, verified against a constant SHA-256 in <code>lib_host_backup_common.sh</code> (<code>HB_BORG_LINUX64_SHA256</code>). On checksum mismatch the binary is discarded and the backup aborts. The downloaded file is cached in the state dir so subsequent backups use path 2."
|
||||
}
|
||||
]
|
||||
},
|
||||
"repoTypes": {
|
||||
"heading": "Repository types",
|
||||
"intro": "The repository type is chosen when a target is added. Each type resolves to a different repository URL that Borg understands.",
|
||||
"rows": [
|
||||
{ "type": "remote", "url": "ssh://USER@HOST/RPATH", "detail": "Repository lives on a remote server that runs <code>borg serve</code>. Requires an SSH connection to that server." },
|
||||
{ "type": "usb", "url": "/mnt/MOUNTPOINT/borgbackup", "detail": "Repository lives on a mounted external disk (typically USB). The mount is resolved via <code>hb_prompt_mounted_path</code>, which detects, mounts or formats USB partitions as needed." },
|
||||
{ "type": "local", "url": "/backup/borgbackup (any absolute path)", "detail": "Repository lives on a local directory. Meaningful only when the target directory is on a separate physical disk — a repository on the same disk as the source protects against operator error but not against disk failure." }
|
||||
{
|
||||
"type": "remote",
|
||||
"url": "ssh://USER@HOST/RPATH",
|
||||
"detail": "Repository lives on a remote server that runs <code>borg serve</code>. Requires an SSH connection to that server."
|
||||
},
|
||||
{
|
||||
"type": "usb",
|
||||
"url": "/mnt/MOUNTPOINT/borgbackup",
|
||||
"detail": "Repository lives on a mounted external disk (typically USB). The mount is resolved via <code>hb_prompt_mounted_path</code>, which detects, mounts or formats USB partitions as needed."
|
||||
},
|
||||
{
|
||||
"type": "local",
|
||||
"url": "/backup/borgbackup (any absolute path)",
|
||||
"detail": "Repository lives on a local directory. Meaningful only when the target directory is on a separate physical disk — a repository on the same disk as the source protects against operator error but not against disk failure."
|
||||
}
|
||||
]
|
||||
},
|
||||
"serverSetup": {
|
||||
@@ -50,30 +78,62 @@
|
||||
"Another Proxmox host on the same LAN, or a VM anywhere reachable over SSH."
|
||||
],
|
||||
"lxcWarningTitle": "LXC as Borg server",
|
||||
"lxcWarningBody": "If the Borg server runs inside an LXC, the container must have a user account with a password.",
|
||||
"lxcWarningBody": "If the Borg server runs inside an LXC, generate-auto requires an account that accepts SSH password authentication during key installation. If password login is unavailable, use manual key installation or the generate-pct alternative through the PVE host.",
|
||||
"requirementsTitle": "Requirements on the server",
|
||||
"requirementsRows": [
|
||||
{ "requirement": "borg binary at <code>/usr/bin/borg</code>", "detail": "The <code>command=\"/usr/bin/borg serve ...\"</code> line ProxMenux writes into <code>authorized_keys</code> hard-codes that path. Installing via APT (<code>apt install borgbackup</code>) puts it there. A standalone binary must be symlinked to <code>/usr/bin/borg</code>." },
|
||||
{ "requirement": "A dedicated user account (typically <code>borg</code>)", "detail": "Owns the repository directory and receives incoming SSH connections. Does not need sudo or shell access — the authorized_keys line disables interactive shells anyway." },
|
||||
{ "requirement": "A writable repository directory", "detail": "The path the operator gives ProxMenux (for example <code>/backup/borgbackup</code>) must exist on the server and be owned by the borg user." },
|
||||
{ "requirement": "SSH daemon accepting the borg user", "detail": "<code>PubkeyAuthentication yes</code> (default). Password authentication is only needed for the one-shot <em>generate-auto</em> flow — after the key is installed, the server can disable password auth entirely." }
|
||||
{
|
||||
"requirement": "borg binary at <code>/usr/bin/borg</code>",
|
||||
"detail": "The <code>command=\"/usr/bin/borg serve ...\"</code> line ProxMenux writes into <code>authorized_keys</code> hard-codes that path. Installing via APT (<code>apt install borgbackup</code>) puts it there. A standalone binary must be symlinked to <code>/usr/bin/borg</code>."
|
||||
},
|
||||
{
|
||||
"requirement": "A dedicated user account (typically <code>borg</code>)",
|
||||
"detail": "Owns the repository directory and receives incoming SSH connections. Does not need sudo or shell access — the authorized_keys line disables interactive shells anyway."
|
||||
},
|
||||
{
|
||||
"requirement": "A writable repository directory",
|
||||
"detail": "The path the operator gives ProxMenux (for example <code>/backup/borgbackup</code>) must exist on the server and be owned by the borg user."
|
||||
},
|
||||
{
|
||||
"requirement": "SSH daemon accepting the borg user",
|
||||
"detail": "<code>PubkeyAuthentication yes</code> (default). Password authentication is only needed for the one-shot <em>generate-auto</em> flow — after the key is installed, the server can disable password auth entirely."
|
||||
}
|
||||
],
|
||||
"minimalSetupTitle": "Minimal server setup",
|
||||
"minimalSetupBody": "On a Debian or Ubuntu Borg server, a working baseline is four commands as root:",
|
||||
"minimalSetupCmd": "apt install borgbackup\nuseradd -m -d /home/borg -s /bin/bash borg\nmkdir -p /backup/borgbackup\nchown borg:borg /backup/borgbackup",
|
||||
"minimalSetupNote": "After this, ProxMenux's <code>generate-auto</code> mode can connect using the borg user's password once, install its own SSH key, and every subsequent backup uses that key. No further server-side configuration is needed — the key restricts itself to <code>borg serve</code> on that path."
|
||||
"minimalSetupNote": "With the server prepared and SSH password authentication available, ProxMenux’s <code>generate-auto</code> mode can install its SSH key using an account permitted to write the borg user’s authorized_keys file. Subsequent backups use that key, restricted to <code>borg serve</code> on the configured path. Without password login, install the key manually or use the PVE-host alternative."
|
||||
},
|
||||
"sshAuth": {
|
||||
"heading": "SSH authentication (client side)",
|
||||
"intro": "Remote Borg repositories are accessed over SSH. The connection runs from the ProxMenux host to a user account on the Borg server that runs <code>borg serve</code>. This user is typically named <code>borg</code>, NOT the admin/root user of the server — the borg-serve command line locks the connection to that specific repository path (see the key strategies below).",
|
||||
"strategiesTitle": "The four key strategies",
|
||||
"strategiesIntro": "When a remote target is added, ProxMenux prompts for the SSH user, host and remote path, then asks how to authenticate. Four modes:",
|
||||
"strategiesTitle": "SSH key strategies",
|
||||
"strategiesIntro": "When a remote target is added, ProxMenux asks for the SSH user, host and remote path, then offers four authentication choices. The additional generate-pct path is offered when password authentication is refused. The modes are described below:",
|
||||
"strategyRows": [
|
||||
{ "mode": "generate-auto", "label": "Recommended", "detail": "ProxMenux generates a new ed25519 keypair at <code>~/.ssh/borg_proxmenux_HOST_ed25519</code>, then uses <code>sshpass</code> to log in ONCE to the server with the admin password and append the public key to <code>~borg/.ssh/authorized_keys</code>. The admin password is only used for this one call — it is never stored." },
|
||||
{ "mode": "generate-manual", "label": "No admin password on this host", "detail": "ProxMenux generates the keypair as above but displays the full <code>authorized_keys</code> line for the operator to paste manually into the server. The admin password never leaves the ProxMenux host because it is never asked for." },
|
||||
{ "mode": "generate-pct", "label": "Borg server is a PVE LXC", "detail": "The Borg server runs inside an LXC on a PVE node. ProxMenux authorises the key via <code>pct exec</code> from the PVE host — root on the PVE host writes into the LXC's <code>~borg/.ssh/authorized_keys</code> without needing SSH into the LXC itself." },
|
||||
{ "mode": "existing", "label": "Use an existing key", "detail": "ProxMenux scans <code>/root/.ssh/</code> and <code>$HOME/.ssh/</code> for parseable ed25519/RSA private keys and lists them. The operator picks one or browses manually to a non-standard path." },
|
||||
{ "mode": "none", "label": "Default SSH config", "detail": "No custom key — Borg relies on the host's default SSH configuration (typically <code>~/.ssh/id_rsa</code> or an SSH agent)." }
|
||||
{
|
||||
"mode": "generate-auto",
|
||||
"label": "Recommended",
|
||||
"detail": "ProxMenux generates or reuses an ed25519 keypair at <code>~/.ssh/borg_proxmenux_HOST_ed25519</code>, then uses <code>sshpass</code> for a login probe and installation of the public key in <code>~borg/.ssh/authorized_keys</code>. The password belongs to an account allowed to write that file, usually root or the borg user. It is used during setup and is not saved."
|
||||
},
|
||||
{
|
||||
"mode": "generate-manual",
|
||||
"label": "No admin password on this host",
|
||||
"detail": "ProxMenux generates the keypair as above but displays the full <code>authorized_keys</code> line for the operator to paste manually into the server. The admin password never leaves the ProxMenux host because it is never asked for."
|
||||
},
|
||||
{
|
||||
"mode": "generate-pct",
|
||||
"label": "Borg server is a PVE LXC",
|
||||
"detail": "The Borg server runs inside an LXC on a PVE node. ProxMenux authorises the key via <code>pct exec</code> from the PVE host — root on the PVE host writes into the LXC's <code>~borg/.ssh/authorized_keys</code> without needing SSH into the LXC itself."
|
||||
},
|
||||
{
|
||||
"mode": "existing",
|
||||
"label": "Use an existing key",
|
||||
"detail": "ProxMenux scans <code>/root/.ssh/</code> and <code>$HOME/.ssh/</code> for parseable ed25519/RSA private keys and lists them. The operator picks one or browses manually to a non-standard path."
|
||||
},
|
||||
{
|
||||
"mode": "none",
|
||||
"label": "Default SSH config",
|
||||
"detail": "No custom key — Borg relies on the host's default SSH configuration (typically <code>~/.ssh/id_rsa</code> or an SSH agent)."
|
||||
}
|
||||
],
|
||||
"restrictTitle": "The authorized_keys line",
|
||||
"restrictBody": "For every generated key, the <code>authorized_keys</code> line ProxMenux writes on the server locks the key to a single borg-serve invocation against the configured repository path:",
|
||||
@@ -84,24 +144,50 @@
|
||||
"heading": "Saved targets",
|
||||
"intro": "A saved target persists the repository configuration under a friendly name so the details do not have to be re-entered. Storage layout in the ProxMenux state directory:",
|
||||
"rows": [
|
||||
{ "file": "borg-targets.txt", "content": "One line per target: <code>NAME|REPO|SSH_KEY_PATH|ENCRYPT_MODE</code>. Read by <code>hb_collect_borg_configs</code> to populate the target-selection menu." },
|
||||
{ "file": "borg-pass-NAME.txt", "content": "Passphrase for the target NAMEd above (<code>chmod 600</code>). Only present when the operator chose repokey encryption." }
|
||||
{
|
||||
"file": "borg-targets.txt",
|
||||
"content": "One line per target: <code>NAME|REPO|SSH_KEY_PATH|ENCRYPT_MODE</code>. Read by <code>hb_collect_borg_configs</code> to populate the target-selection menu."
|
||||
},
|
||||
{
|
||||
"file": "borg-pass-NAME.txt",
|
||||
"content": "Passphrase for the target NAMEd above (<code>chmod 600</code>). Only present when the operator chose repokey encryption."
|
||||
}
|
||||
],
|
||||
"outro": "Saving is optional — the operator can decline the save prompt for a one-shot backup that leaves no credentials on the host."
|
||||
"outro": "Saving the target is optional. Declining the save prompt omits the named target configuration, but does not remove a generated SSH key. If repokey encryption is enabled without a saved target, ProxMenux still saves the passphrase in the legacy borg-pass.txt file."
|
||||
},
|
||||
"encryption": {
|
||||
"heading": "Encryption",
|
||||
"body": "Repositories are initialised with an encryption mode via <code>hb_borg_init_if_needed</code>: the modern <code>borg repo-create -e MODE</code> when available, or the legacy <code>borg init --encryption=MODE</code>. ProxMenux exposes two modes: <code>repokey</code> (default) and <code>none</code>. In <code>repokey</code> mode Borg stores the encryption key inside the repository itself; access requires a passphrase, which ProxMenux prompts for twice with match validation and stores at <code>borg-pass-NAME.txt</code>. A mandatory acknowledgement dialog is shown after the passphrase is saved: the passphrase is the only way to access the encrypted archives, and losing it makes every archive in the repository unrecoverable."
|
||||
"body": "Repositories are initialised with an encryption mode via <code>hb_borg_init_if_needed</code>: the modern <code>borg repo-create -e MODE</code> when available, or the legacy <code>borg init --encryption=MODE</code>. ProxMenux exposes two modes: <code>repokey</code> (default) and <code>none</code>. In <code>repokey</code> mode Borg stores the encryption key inside the repository itself; access requires a passphrase, which ProxMenux prompts for twice with match validation and stores at <code>borg-pass-NAME.txt</code> for saved targets (or in the legacy file described above). A mandatory acknowledgement dialog is shown after the passphrase is saved: the passphrase is the only way to access the encrypted archives, and losing it makes every archive in the repository unrecoverable."
|
||||
},
|
||||
"runtimeEnv": {
|
||||
"heading": "Runtime environment",
|
||||
"intro": "ProxMenux exports the following environment variables before invoking <code>borg</code>. They configure the connection and unlock the repository without embedding secrets in the command line.",
|
||||
"rows": [
|
||||
{ "var": "BORG_RSH", "value": "ssh -i SSH_KEY_PATH -o StrictHostKeyChecking=accept-new", "purpose": "The remote-shell command Borg uses for SSH-served repositories. Set only when a custom key was selected; otherwise unset so Borg uses the default SSH configuration." },
|
||||
{ "var": "BORG_PASSPHRASE", "value": "(passphrase from borg-pass-NAME.txt)", "purpose": "Unlocks the repokey. Set only when the target uses <code>repokey</code> encryption. Never appears in the process argument list." },
|
||||
{ "var": "BORG_ENCRYPT_MODE", "value": "repokey | none", "purpose": "Used by <code>hb_borg_init_if_needed</code> when initialising a repository that does not yet exist. Ignored by <code>borg create</code> on existing repositories." },
|
||||
{ "var": "BORG_RELOCATED_REPO_ACCESS_IS_OK", "value": "yes", "purpose": "Suppresses the interactive prompt Borg raises when the repository URL differs from the URL it was originally reached from (common after a mount-point rename or SSH-host reachable via new address)." },
|
||||
{ "var": "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK", "value": "yes", "purpose": "Suppresses the interactive prompt Borg raises the first time an unencrypted repository is accessed from a new client." }
|
||||
{
|
||||
"var": "BORG_RSH",
|
||||
"value": "ssh -i SSH_KEY_PATH -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new [-p PORT]",
|
||||
"purpose": "The remote-shell command Borg uses for SSH repositories. A selected key adds the identity options; a non-default port adds -p PORT. With no custom key and the default port, the variable is unset so Borg uses the host’s default SSH configuration."
|
||||
},
|
||||
{
|
||||
"var": "BORG_PASSPHRASE",
|
||||
"value": "(passphrase from borg-pass-NAME.txt)",
|
||||
"purpose": "Unlocks the repokey. Set only when the target uses <code>repokey</code> encryption. Never appears in the process argument list."
|
||||
},
|
||||
{
|
||||
"var": "BORG_ENCRYPT_MODE",
|
||||
"value": "repokey | none",
|
||||
"purpose": "Used by <code>hb_borg_init_if_needed</code> when initialising a repository that does not yet exist. Ignored by <code>borg create</code> on existing repositories."
|
||||
},
|
||||
{
|
||||
"var": "BORG_RELOCATED_REPO_ACCESS_IS_OK",
|
||||
"value": "yes",
|
||||
"purpose": "Suppresses the interactive prompt Borg raises when the repository URL differs from the URL it was originally reached from (common after a mount-point rename or SSH-host reachable via new address)."
|
||||
},
|
||||
{
|
||||
"var": "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK",
|
||||
"value": "yes",
|
||||
"purpose": "Suppresses the interactive prompt Borg raises the first time an unencrypted repository is accessed from a new client."
|
||||
}
|
||||
]
|
||||
},
|
||||
"archiveFormat": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "Same payload, three backends",
|
||||
"body": "Every backup produces the same three payloads (rootfs, manifest, applications). What changes between destinations is how those payloads are stored and how the operator retrieves them for a restore. Each backend is implemented as a separate function in <code>backup_host.sh</code> — <code>_bk_local</code>, <code>_bk_pbs</code>, <code>_bk_borg</code> — but all four share the same staging step (<code>hb_prepare_staging</code>) and the same restore code path. The choice of destination only affects the write and the retrieval; it does not change what a restore does or how it consumes the archive."
|
||||
"body": "Every backup produces the same three payloads (rootfs, manifest, applications). What changes between destinations is how those payloads are stored and how the operator retrieves them for a restore. Each backend is implemented as a separate function in <code>backup_host.sh</code> — <code>_bk_local</code>, <code>_bk_pbs</code>, <code>_bk_borg</code> — but all three share the same staging step (<code>hb_prepare_staging</code>) and the same restore code path. The choice of destination only affects the write and the retrieval; it does not change what a restore does or how it consumes the archive."
|
||||
},
|
||||
"comparison": {
|
||||
"heading": "Feature comparison",
|
||||
@@ -29,8 +29,8 @@
|
||||
{
|
||||
"feature": "Compression",
|
||||
"local": "zstd (default level) via <code>tar --zstd</code>. gzip fallback.",
|
||||
"pbs": "PBS-managed. The client streams uncompressed; the server chunks + compresses.",
|
||||
"borg": "Borg's built-in (lz4 default). Configurable at repo init."
|
||||
"pbs": "Managed by proxmox-backup-client. ProxMenux does not override compression settings.",
|
||||
"borg": "Borg’s built-in compression. ProxMenux invokes borg create without overriding compression settings."
|
||||
},
|
||||
{
|
||||
"feature": "Deduplication",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"backupCommand": {
|
||||
"heading": "The backup command",
|
||||
"intro": "The upload is a single invocation of <code>proxmox-backup-client backup</code>. ProxMenux runs it inside an <code>env</code> wrapper so credentials never appear in the process argument list.",
|
||||
"intro": "The upload is a single invocation of <code>proxmox-backup-client backup</code>. ProxMenux uses <code>env</code> to pass the password, encryption password and fingerprint to the client as environment variables.",
|
||||
"cmd": "env \\\n PBS_PASSWORD=\"$HB_PBS_SECRET\" \\\n PBS_ENCRYPTION_PASSWORD=\"$HB_PBS_ENC_PASS\" \\\n PBS_FINGERPRINT=\"$HB_PBS_FINGERPRINT\" \\\n proxmox-backup-client backup \\\n hostcfg.pxar:$staging_root \\\n --repository USER@REALM@HOST:DATASTORE \\\n --backup-type host \\\n --backup-id hostcfg-HOSTNAME \\\n --backup-time BACKUP-EPOCH \\\n [--keyfile /usr/local/share/proxmenux/pbs-key.conf]",
|
||||
"backupIdTitle": "Backup ID naming",
|
||||
"backupIdBody": "The default backup ID is <code>hostcfg-HOSTNAME</code>. The operator is asked to confirm or edit it before the upload; any characters outside <code>[A-Za-z0-9_-]</code> are stripped and trailing dashes are trimmed. Reusing the same ID across runs is intentional — PBS treats the ID as a <em>group</em>, and every subsequent backup appears as a new backup inside that group, sharing dedup with prior runs.",
|
||||
@@ -61,13 +61,13 @@
|
||||
"modesSharedTitle": "Shared keyfile (import on every host)",
|
||||
"modesSharedBody": "One master keyfile generated once and installed on every host via the <em>Use an existing keyfile</em> option. Management is simpler: a single secret to safeguard, and any host can decrypt the archives of any other (useful for consolidation, cross-host restore drills or centralised backup verification). The trade-off is that a leak of the shared keyfile exposes every host at once. Recommended for homelabs and for fleets where all hosts have the same owner and trust boundary.",
|
||||
"recoveryTitle": "Upload key to PBS — the operator decides",
|
||||
"recoveryBody": "Right after the keyfile is chosen, ProxMenux asks a single explicit question — <em>Upload key to PBS?</em> — with two answers. <strong>Nothing gets uploaded to PBS until the operator answers Yes.</strong> The default is <em>No, keep local only</em>: the keyfile stays only at the canonical local path, ProxMenux never touches PBS with any recovery artefact, and the operator handles the offsite copy themselves (the Monitor's <em>Download keyfile</em> button in the PBS destination row provides one on demand). Choosing <em>Yes, upload</em> prompts for a recovery passphrase — twice, with match validation — and ProxMenux wraps the keyfile with that passphrase using AES-256-CBC and PBKDF2 (600 000 iterations, random salt) to produce <code>pbs-key.recovery.enc</code>. Every subsequent encrypted backup uploads that envelope to PBS as a paired backup group. Neither the passphrase nor a plaintext copy of the keyfile ever leaves the host. The choice can be changed later from the Monitor at any time — the escrow toggle inline in the PBS destination row flips Yes/No and applies immediately (Start uploading, Stop uploading, or Update passphrase, matching the current state).",
|
||||
"recoveryBody": "Right after the keyfile is chosen, ProxMenux asks a single explicit question — <em>Upload key to PBS?</em> — with two answers. <strong>No recovery artifact is uploaded to PBS until the operator answers Yes.</strong> The default is <em>No, keep local only</em>: the keyfile stays at the canonical local path, ProxMenux uploads no recovery artifact to PBS, and the operator handles the offsite copy themselves (the Monitor’s <em>Download keyfile</em> button in the PBS destination row provides one on demand). Choosing <em>Yes, upload</em> prompts for a recovery passphrase — twice, with match validation — and ProxMenux wraps the keyfile with that passphrase using AES-256-CBC and PBKDF2 (600 000 iterations, random salt) to produce <code>pbs-key.recovery.enc</code>. Every subsequent encrypted backup uploads that envelope to PBS as a paired backup group. Neither the passphrase nor a plaintext copy of the keyfile is sent to PBS during these uploads. The choice can be changed later from the Monitor at any time — the escrow toggle inline in the PBS destination row flips Yes/No and applies immediately (Start uploading, Stop uploading, or Update passphrase, matching the current state).",
|
||||
"blobUploadTitle": "Paired backup group on PBS",
|
||||
"blobUploadBody1": "When the escrow mode is <em>Yes, upload</em>, ProxMenux uploads the recovery envelope to PBS after every encrypted backup as a second, paired backup group with the same name as the host group but ending in <code>-keyrecovery</code>. The two groups sit next to each other in the datastore listing — one for host backups, one for recovery envelopes. <strong>The envelope never leaves the host in plaintext</strong>: encryption happens on the host itself with AES-256-CBC and PBKDF2 before any bytes go on the wire (<code>openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt</code>). PBS receives only the already-encrypted envelope.",
|
||||
"envelopeSecurityTitle": "Is uploading the keyrecovery file to PBS a security risk?",
|
||||
"envelopeSecurityBody": "The keyrecovery file is encrypted in transit and at rest. The recovery passphrase never leaves the host: encryption happens locally before the upload, and PBS only ever sees the already-encrypted result. A PBS administrator — or anyone with access to the datastore — can download the keyrecovery file, but without the recovery passphrase they cannot read the keyfile inside: it is opaque ciphertext. Reconstructing the keyfile requires both pieces at once, the keyrecovery file and the passphrase, and only the operator holds both.",
|
||||
"blobUploadConstraintTitle": "Why two groups instead of a single encrypted one",
|
||||
"blobUploadConstraintBody": "A single <code>proxmox-backup-client backup</code> invocation encrypts every one of its archives with the same keyfile, or none at all — there is no in-between. The host backup has to be encrypted with the host keyfile, but the recovery envelope cannot ride along in that same upload, because it would end up encrypted with the very keyfile it contains: on a freshly reinstalled host there would be no way to open it (you would need the keyfile to decrypt the keyfile). That is why the two artefacts are uploaded independently and appear as two separate groups: the host backup is encrypted by PBS with the keyfile, and the recovery envelope is encrypted by ProxMenux with the passphrase before upload. Two different encryption layers protecting two different assets.",
|
||||
"blobUploadConstraintBody": "A single <code>proxmox-backup-client backup</code> invocation encrypts every one of its archives with the same keyfile, or none at all — there is no in-between. The host backup has to be encrypted with the host keyfile, but the recovery envelope cannot ride along in that same upload, because it would end up encrypted with the very keyfile it contains: on a freshly reinstalled host there would be no way to open it (you would need the keyfile to decrypt the keyfile). That is why the two artefacts are uploaded independently and appear as two separate groups: the host backup is encrypted by the PBS client with the keyfile, and the recovery envelope is encrypted by ProxMenux with the passphrase before upload. Two different encryption layers protecting two different assets.",
|
||||
"blobUploadImageAlt": "PBS UI showing the hostcfg-HOSTNAME and hostcfg-HOSTNAME-keyrecovery backup groups adjacent in the datastore listing.",
|
||||
"blobUploadImageCaption": "PBS UI — the paired backup groups. The main group holds the host backups; the -keyrecovery group holds the passphrase-wrapped keyfile.",
|
||||
"recoverTitle": "Recovery on a fresh host",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{
|
||||
"id": "clave-cifrado",
|
||||
"term": "Keyfile (encryption key)",
|
||||
"def": "Binary file that Proxmox Backup Server uses to encrypt the <a href=\"#chunk\">chunks</a> of each backup before uploading them. Without this file, no encrypted backup can be read. ProxMenux stores it at <code>/usr/local/share/proxmenux/pbs-key.conf</code> with mode <code>600</code>. This is the asset that has to be protected; the <a href=\"#sobre-recuperacion\">recovery envelope</a> exists precisely so that this file can be rescued if it is lost."
|
||||
"def": "Key file used by proxmox-backup-client to encrypt the <a href=\"#chunk\">chunks</a> before uploading them to PBS. Without the matching key, encrypted backup data cannot be read. ProxMenux stores the file at <code>/usr/local/share/proxmenux/pbs-key.conf</code> with mode <code>600</code>. Protect it: the <a href=\"#sobre-recuperacion\">recovery envelope</a> is designed to recover this file if it is lost."
|
||||
},
|
||||
{
|
||||
"id": "contrasena-keyfile",
|
||||
@@ -156,7 +156,7 @@
|
||||
{
|
||||
"id": "modo-adjunto",
|
||||
"term": "Attach mode",
|
||||
"def": "Scheduled job with no schedule of its own; it fires automatically whenever an existing PVE <code>vzdump</code> task runs. It inherits the schedule and retention from that parent task. Supported for Local and PBS destinations only."
|
||||
"def": "Job without its own timer. A PVE <code>vzdump</code> job-end hook triggers it after guest backups when the destination storage matches. The selected parent supplies storage and initial retention, but the hook matches storage rather than parent job ID; another task using that storage may also trigger it. Supported for Local and PBS only."
|
||||
},
|
||||
{
|
||||
"id": "timer-systemd",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "One archive, three payloads",
|
||||
"body": "Every backup produces a directory layout with three well-defined payloads under a single staging root. The archive uploaded to the destination (local <code>.tar.zst</code>, PBS backup or Borg archive) contains this exact layout. The restore reads the three payloads independently, in a specific order that guarantees correctness: <strong>rootfs</strong> is copied first to lay down configuration, <strong>the manifest</strong> is consulted to detect drift and decide what to skip, and <strong>the application inventory</strong> drives the post-boot reinstall pass. No cross-payload dependencies — each one can be inspected or extracted independently."
|
||||
"body": "Every backup produces a directory layout with three well-defined payloads under a single staging root. The archive uploaded to the destination (local <code>.tar.zst</code>, PBS backup or Borg archive) contains this exact layout. The restore uses all three: <strong>the manifest</strong> is consulted before applying changes to detect drift and decide what to skip; <strong>rootfs</strong> supplies the configuration files, applied live or deferred until reboot; and <strong>the application inventory</strong> drives package installation and the post-boot component reinstall. Each payload can be inspected or extracted independently."
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Archive layout",
|
||||
@@ -26,7 +26,7 @@
|
||||
"heading": "The rootfs payload",
|
||||
"intro": "The <code>rootfs/</code> tree is a <strong>plain filesystem copy</strong> produced by <code>rsync</code> from the source host. It contains a curated <strong>default profile</strong> of paths that matter for a Proxmox restore, plus any <strong>custom paths</strong> added by the user to the backup job or interactive session. The set is deliberately narrow: only paths that either <em>hold configuration</em> or <em>hold state that Proxmox cannot regenerate on its own</em>.",
|
||||
"defaultProfileTitle": "The default profile",
|
||||
"defaultProfileBody": "The default profile is defined by <code>hb_default_profile_paths</code> in <code>lib_host_backup_common.sh</code>. It covers eight categories that together describe a working Proxmox host:",
|
||||
"defaultProfileBody": "The default profile is defined by <code>hb_default_profile_paths</code> in <code>lib_host_backup_common.sh</code>. It covers eight fixed categories, plus conditional ZFS state, that together describe a working Proxmox host:",
|
||||
"categoriesTitle": "Path categories",
|
||||
"categoryRows": [
|
||||
{
|
||||
@@ -125,7 +125,7 @@
|
||||
},
|
||||
"applications": {
|
||||
"heading": "The application inventory",
|
||||
"intro": "Two files under <code>metadata/</code> catalogue everything installed on the source that is not part of the base Proxmox VE package set. The restore uses them to reproduce the exact set of user-installed software on the target, using either APT or ProxMenux's own installers depending on how the software was originally installed.",
|
||||
"intro": "Two inventories catalogue the software installed on the source beyond the base Proxmox VE package set: the package list under <code>metadata/</code> and the component registry inside rootfs, detailed below. The restore uses them to reproduce user-installed software with APT or ProxMenux’s own installers, depending on how it was originally installed.",
|
||||
"packagesTitle": "packages.manual.list",
|
||||
"packagesBody": "A plain-text list produced by <code>apt-mark showmanual</code>: every APT package that was <em>explicitly installed</em> on the source host, sorted alphabetically. This excludes packages installed as dependencies of the base Proxmox VE ISO (they are re-pulled automatically by the target's own APT). Read by <code>_rs_run_complete_extras</code> during restore, filtered through a three-pass <em>cascade-safe</em> filter (<code>dpkg -s</code> for already-installed, sibling-major detection for library packages, <code>apt-get install --simulate</code> for cascade-remove risk), then installed with <code>apt-get install -y</code>.",
|
||||
"componentsTitle": "components_status.json (part of the rootfs)",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "The restore reproduces the source, not just the files",
|
||||
"body": "Restoring a ProxMenux backup is not an extraction. Files are only the first step: after the filesystem is in place, the restore reads the manifest to detect differences between source and target, filters paths that would break the target's boot, hydrates user-authored tuning that cannot be copied verbatim, and — after the mandatory reboot — reinstalls every component the source had (NVIDIA driver, Coral TPU, GPU tools) against the target's own kernel. What the user picks from the menu is a single archive; what actually happens involves the compatibility check, the path classification, the on-boot dispatcher and the post-boot pass working together to produce a host that behaves like the source."
|
||||
"body": "Restoring a ProxMenux backup involves more than extracting files. Before applying changes, the restore reads the manifest to detect differences between source and target, filters paths that could break the target’s boot and adapts user-authored tuning that cannot be copied verbatim. After the required reboot, it attempts to reinstall the recorded supported components (NVIDIA driver, Coral TPU, GPU tools) for the target’s running kernel. A single selected archive drives the compatibility check, path classification, on-boot dispatcher and post-boot work. Review the per-component results and logs to confirm the outcome."
|
||||
},
|
||||
"threeActions": {
|
||||
"heading": "Three actions on a backup",
|
||||
@@ -52,7 +52,7 @@
|
||||
},
|
||||
{
|
||||
"output": "Rollback plan",
|
||||
"detail": "Computed by <code>compute_rollback_plan.sh</code>. Lists VMs, LXCs and components present on the target but not in the backup. The user opts in during the confirmation dialog to have these removed as part of the restore, so the target ends up matching the backup exactly."
|
||||
"detail": "Computed by <code>compute_rollback_plan.sh</code>. Lists VMs, LXCs and components present on the target but absent from the backup. The confirmation dialog offers optional removal of the extra guests before reboot. Component removal remains manual, and guest deletion can fail, so this does not guarantee an exact match with the backup."
|
||||
}
|
||||
],
|
||||
"reportBody": "The check also emits a structured report (<code>HB_COMPAT_RESULTS</code>) categorised as PASS / INFO / WARN / FAIL. WARN and FAIL entries surface in the pre-restore panel; the restore refuses to proceed only when a FAIL is present that the user cannot resolve by clicking Continue."
|
||||
@@ -81,11 +81,11 @@
|
||||
},
|
||||
{
|
||||
"class": "reboot",
|
||||
"detail": "Paths applied <strong>immediately</strong> as well, but whose actual effect only kicks in on the <strong>next boot</strong>: the kernel only reads <code>/etc/default/grub</code> when the bootloader starts, <code>/etc/fstab</code> when filesystems are mounted at boot, <code>/etc/modules</code> when modules are loaded, etc. The file is in place the moment it is applied but the system has to reboot to consume it. Examples: <code>/etc/default/grub</code>, <code>/etc/kernel</code>, <code>/etc/modules</code>, <code>/etc/fstab</code>, <code>/etc/zfs</code>, <code>/etc/initramfs-tools</code>."
|
||||
"detail": "Paths deferred to the <strong>next boot</strong>, rather than applied <strong>immediately</strong> by this restore flow. They affect boot-time configuration: bootloader generation reads <code>/etc/default/grub</code>, filesystem mounting uses <code>/etc/fstab</code>, and module loading uses <code>/etc/modules</code>. The pending files are applied after reboot and the required boot artifacts are then regenerated. Examples: <code>/etc/default/grub</code>, <code>/etc/kernel</code>, <code>/etc/modules</code>, <code>/etc/fstab</code>, <code>/etc/zfs</code>, <code>/etc/initramfs-tools</code>."
|
||||
},
|
||||
{
|
||||
"class": "dangerous",
|
||||
"detail": "Paths that are <strong>NOT applied on the running system</strong> — a live write could corrupt state or drop the active connection. These are staged in the pending set and written by the post-boot dispatcher after the reboot, when the cluster is up but the system is not yet fully in use. Examples: <code>/etc/pve</code> (pmxcfs is a live FUSE mount; writing directly to it bypasses it), <code>/var/lib/pve-cluster</code> (live cluster data), <code>/etc/network</code> (could reconfigure the very interface the user is connected on over SSH and drop the session)."
|
||||
"detail": "Paths that are <strong>NOT applied on the running system</strong> — a live write could corrupt state or drop the active connection. These are staged in the pending set and written by the post-boot dispatcher after the reboot, when the cluster is up but the system is not yet fully in use. Examples: <code>/etc/pve</code> (pmxcfs is a live FUSE mount holding active cluster configuration), <code>/var/lib/pve-cluster</code> (live cluster data), <code>/etc/network</code> (could reconfigure the very interface the user is connected on over SSH and drop the session)."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -131,7 +131,7 @@
|
||||
},
|
||||
{
|
||||
"task": "Component auto-reinstall",
|
||||
"detail": "Reads the restored <code>components_status.json</code> and iterates over the registered installers (nvidia_driver, coral_driver, amdgpu_top, intel_gpu_tools). Each installer runs in <code>--auto-reinstall</code> mode, reads its previously-recorded version from the state file, and rebuilds against the target's current kernel. The interactive flow does not do this — reinstalling drivers against a kernel that has not yet booted is meaningless."
|
||||
"detail": "Reads the restored <code>components_status.json</code> and iterates over the registered supported installers (nvidia_driver, coral_driver, amdgpu_top, intel_gpu_tools). Each selected installer runs in <code>--auto-reinstall</code> mode, reads its recorded state and reinstalls for the target’s environment, rebuilding kernel modules when applicable. This is deferred until after reboot so the installers use the kernel then running on the target. Individual failures are recorded and do not stop the next component."
|
||||
},
|
||||
{
|
||||
"task": "Boot sanity check",
|
||||
@@ -145,7 +145,7 @@
|
||||
},
|
||||
"liveProgress": {
|
||||
"heading": "Live progress in the Monitor's Backups tab",
|
||||
"body": "After the reboot the Backups tab of ProxMenux Monitor renders a live post-restore card that reflects the state of <code>apply_cluster_postboot.service</code> as it runs. The card is driven by <code>/var/lib/proxmenux/restore-state.json</code>, which the dispatcher updates at every milestone (apply cluster config, rebuild initramfs, update bootloader, per-component reinstall, boot sanity check, finalize).",
|
||||
"body": "After the reboot the Backups tab of ProxMenux Monitor renders a live post-restore card that reflects the state of <code>proxmenux-apply-cluster-postboot.service</code> as it runs. The card is driven by <code>/var/lib/proxmenux/restore-state.json</code>, which the dispatcher updates at every milestone (apply cluster config, rebuild initramfs, update bootloader, per-component reinstall, boot sanity check, finalize).",
|
||||
"fields": [
|
||||
{
|
||||
"name": "Status badge",
|
||||
@@ -184,9 +184,9 @@
|
||||
},
|
||||
"postbootExample": {
|
||||
"heading": "What the post-boot looks like on the console",
|
||||
"body": "On the host's physical console, a successful dispatcher run ends with a <code>[ OK ] Finished proxmenux-apply-cluster-postboot.service</code> line. When that line appears — usually alongside the <code>OK</code>s for <code>multi-user.target</code> and <code>graphical.target</code> — the restore is completely finished: components reinstalled, boot artifacts regenerated, cluster reconciled.",
|
||||
"body": "On the host’s physical console, a dispatcher run that exits successfully ends with <code>[ OK ] Finished proxmenux-apply-cluster-postboot.service</code>, often near the <code>OK</code>s for <code>multi-user.target</code> and <code>graphical.target</code>. This marks the end of the dispatcher process. Individual component failures or boot warnings can still have been recorded; check the Monitor’s component states and logs before treating the host as fully restored.",
|
||||
"imageAlt": "Proxmox physical console showing '[ OK ] Finished proxmenux-apply-cluster-postboot.service - ProxMenux Apply Cluster Configs (post-boot).' followed by the OK for multi-user.target and graphical.target, with the host login prompt above.",
|
||||
"imageCaption": "Physical console after a successful post-boot. The 'Finished proxmenux-apply-cluster-postboot.service' line is the signal that the restore flow is fully complete — the same event the 'Host restore finished' notification surfaces in the Monitor."
|
||||
"imageCaption": "Physical console after the dispatcher process finishes. 'Finished proxmenux-apply-cluster-postboot.service' marks process completion, also reported by 'Host restore finished' in the Monitor. Component results and warnings still need review."
|
||||
},
|
||||
"tenMinutes": {
|
||||
"heading": "Why the last ten minutes matter",
|
||||
@@ -228,11 +228,11 @@
|
||||
"detail": "Cheap. The user learns the run completed the moment the notification fires."
|
||||
}
|
||||
],
|
||||
"outroBody": "The window matters because the user can log in during this time and see missing tools (<code>nvidia-smi</code> not found, <code>coral</code> not detected). This is expected — the reinstall is still running in the background. The completion notification signals when everything is ready."
|
||||
"outroBody": "During this window a login may show missing tools (<code>nvidia-smi</code> not found, <code>coral</code> not detected) because reinstall work is still running in the background. The completion notification marks the end of that work; check component results, warnings and logs to distinguish success from individual failures."
|
||||
},
|
||||
"destructiveRollback": {
|
||||
"heading": "The optional destructive rollback",
|
||||
"body": "A restore is additive by default: if the target has VMs, LXCs or components that are not in the backup, they survive. This preserves work but can leave stale hostpci entries, orphan components or VMs the user no longer wants. When drift is detected between target and backup, the confirmation dialog offers a second yes/no: <em>Execute destructive rollback?</em>. Accepting it triggers <code>_rs_execute_rollback</code>, which deletes the VMs, LXCs and components that exist on the target but not in the backup. The rollback runs BEFORE the reboot so that the pending restore machinery sees a clean state. This is opt-in — the default is No, and the user sees exactly what would be removed listed by name (VMID and CTID) before deciding."
|
||||
"body": "A restore is additive by default: extra VMs, LXCs and components on the target remain. When differences are detected, the confirmation dialog offers <em>Execute destructive rollback?</em>, defaulting to No. Accepting calls <code>_rs_execute_rollback</code> before reboot to attempt deletion of listed extra VMs and LXCs. Components are flagged for manual uninstall because automatic uninstall is not implemented. Guest deletion failures are reported and processing continues, so an exact match with the backup is not guaranteed. Review the listed names and VMIDs/CTIDs before choosing this destructive option."
|
||||
},
|
||||
"logs": {
|
||||
"heading": "Where the logs live",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"attachBadge": {
|
||||
"title": "Attach mode — recommended when a PVE vzdump task already exists",
|
||||
"body": "When a PVE vzdump backup task is already configured for the VMs and LXCs on this node, attaching the host backup to that task guarantees the host configuration is captured in the <strong>same window</strong> as the guests. On restore, the guest configs come from the host backup (they live under <code>/etc/pve</code>), and the guest disks come from the vzdump backup taken alongside — both sets are consistent with each other, so a full-node recovery can reproduce the host and re-attach every guest without version drift."
|
||||
"body": "When a PVE vzdump backup task is already configured for the VMs and LXCs on this node, attaching the host backup runs it after the guest backup job, in the <strong>same backup cycle</strong>. On restore, guest configurations come from the host backup (under <code>/etc/pve</code>) and guest disks come from the vzdump backups taken in that cycle. This helps coordinate a full-node recovery, but is not an atomic snapshot: changes made during the backup cycle can leave differences between host configuration and guest disk state."
|
||||
},
|
||||
"frequencyBadge": {
|
||||
"title": "Recommended frequency",
|
||||
@@ -31,29 +31,63 @@
|
||||
"modes": {
|
||||
"heading": "The two modes",
|
||||
"rows": [
|
||||
{ "mode": "New scheduled job", "backends": "Local, PBS, Borg", "schedule": "Own <code>OnCalendar</code> expression (systemd calendar syntax — e.g. <code>daily</code>, <code>Mon..Fri 03:00</code>).", "retention": "Prompted separately: <code>keep-last</code>, <code>keep-hourly</code>, <code>keep-daily</code>, <code>keep-weekly</code>, <code>keep-monthly</code>, <code>keep-yearly</code>. Applied by the runner after each successful backup." },
|
||||
{ "mode": "Attach to a PVE vzdump job", "backends": "Local, PBS (Borg has no PVE-side scheduler)", "schedule": "Inherited from the parent PVE job. No systemd timer is installed on the ProxMenux side.", "retention": "Inherited from the parent's <code>prune-backups</code> configuration (mapped one-to-one to the runner's <code>KEEP_*</code> variables via <code>hb_pve_prune_to_keep_env</code>)." }
|
||||
{
|
||||
"mode": "New scheduled job",
|
||||
"backends": "Local, PBS, Borg",
|
||||
"schedule": "Own <code>OnCalendar</code> expression (systemd calendar syntax — e.g. <code>daily</code>, <code>Mon..Fri 03:00</code>).",
|
||||
"retention": "Prompted separately: <code>keep-last</code>, <code>keep-hourly</code>, <code>keep-daily</code>, <code>keep-weekly</code>, <code>keep-monthly</code>, <code>keep-yearly</code>. Applied by the runner after each successful backup."
|
||||
},
|
||||
{
|
||||
"mode": "Attach to a PVE vzdump job",
|
||||
"backends": "Local, PBS (Borg has no PVE-side scheduler)",
|
||||
"schedule": "Inherited from the parent PVE job. No systemd timer is installed on the ProxMenux side.",
|
||||
"retention": "Inherited from the parent's <code>prune-backups</code> configuration (mapped one-to-one to the runner's <code>KEEP_*</code> variables via <code>hb_pve_prune_to_keep_env</code>)."
|
||||
}
|
||||
]
|
||||
},
|
||||
"attachDetail": {
|
||||
"heading": "How attach mode works",
|
||||
"intro": "PVE writes <code>vzdump</code> tasks to <code>/etc/pve/jobs.cfg</code> — one stanza per task, each pointing at a storage where the VM and LXC dumps land. Attach mode requires that storage to be a backend ProxMenux understands (Local or PBS); when the task fires, ProxMenux runs alongside it.",
|
||||
"intro": "PVE writes <code>vzdump</code> tasks to <code>/etc/pve/jobs.cfg</code> — one stanza per task, each pointing at a storage where the VM and LXC dumps land. Attach mode requires a backend ProxMenux understands (Local or PBS). The host backup runs from the job-end hook after the guest backup job finishes.",
|
||||
"steps": [
|
||||
{ "step": "1", "detail": "During job creation, ProxMenux lists compatible parent PVE tasks via <code>hb_pve_list_vzdump_jobs_for_backend</code>. The user picks one." },
|
||||
{ "step": "2", "detail": "The job's <code>.env</code> is written with <code>PVE_PARENT_JOB</code>, <code>PVE_STORAGE</code> and the inherited <code>KEEP_*</code> values; no systemd timer is created." },
|
||||
{ "step": "3", "detail": "<code>hb_install_vzdump_hook</code> registers a script-hook in <code>/etc/vzdump.conf</code>. When PVE runs any vzdump task, the hook script fires; if the <code>$STOREID</code> passed to it matches an attached job's <code>PVE_STORAGE</code>, the runner is invoked for that job." },
|
||||
{ "step": "4", "detail": "The archive lands in the same storage the vzdump dumps just wrote to: <code>path/dump/</code> for Local, or the PBS repository configured on the PVE storage entry." }
|
||||
{
|
||||
"step": "1",
|
||||
"detail": "During job creation, ProxMenux lists compatible parent PVE tasks via <code>hb_pve_list_vzdump_jobs_for_backend</code>. The user picks one."
|
||||
},
|
||||
{
|
||||
"step": "2",
|
||||
"detail": "The job's <code>.env</code> is written with <code>PVE_PARENT_JOB</code>, <code>PVE_STORAGE</code> and the inherited <code>KEEP_*</code> values; no systemd timer is created."
|
||||
},
|
||||
{
|
||||
"step": "3",
|
||||
"detail": "<code>hb_install_vzdump_hook</code> registers a script-hook in <code>/etc/vzdump.conf</code>. In the job-end phase of any vzdump task, it compares the supplied <code>$STOREID</code> with each enabled attached job’s <code>PVE_STORAGE</code> and invokes the runner for matching jobs. The match is by storage, not by the selected parent job ID."
|
||||
},
|
||||
{
|
||||
"step": "4",
|
||||
"detail": "The archive lands in the same storage the vzdump dumps just wrote to: <code>path/dump/</code> for Local, or the PBS repository configured on the PVE storage entry."
|
||||
}
|
||||
],
|
||||
"outroBody": "Attach mode has one operational implication: disabling or deleting the parent PVE task also disables the host backup — there is no timer of its own to fall back on. The job entry stays on disk so it can be re-attached later."
|
||||
"outroBody": "Attach mode has no timer of its own. Disabling or deleting the selected parent PVE task stops that task’s trigger, but another vzdump task using the same storage can still trigger the attached host backup. Disable or delete the ProxMenux job itself to prevent that. The job entry remains on disk when disabled and can be re-enabled or re-attached later."
|
||||
},
|
||||
"storageLayout": {
|
||||
"heading": "Files that make up a job",
|
||||
"intro": "Every scheduled job is fully described by three or four files on disk. Reading them gives the full configuration of the job without relying on the interface.",
|
||||
"rows": [
|
||||
{ "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.env", "content": "Backend, backup ID or destination, schedule (New mode) or PVE parent (Attach mode), enabled flag, retention <code>KEEP_*</code> values, credentials pointers." },
|
||||
{ "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.paths", "content": "One absolute path per line — the frozen selection resolved from the profile at job-creation time. Editing the file re-runs the job with the new selection on the next timer fire." },
|
||||
{ "path": "/etc/systemd/system/proxmenux-backup-JOB_ID.timer + .service", "content": "New mode only. The service invokes <code>run_scheduled_backup.sh JOB_ID</code>. The timer schedules it with <code>Persistent=true</code> (missed fires run at next boot) and a small <code>RandomizedDelaySec=120</code> to spread load when multiple jobs share the same OnCalendar expression." },
|
||||
{ "path": "/etc/vzdump.conf script-hook", "content": "Attach mode only. Installed once by <code>hb_install_vzdump_hook</code>; matches every attached job's <code>PVE_STORAGE</code> against the <code>$STOREID</code> PVE passes to the hook script." }
|
||||
{
|
||||
"path": "/var/lib/proxmenux/backup-jobs/JOB_ID.env",
|
||||
"content": "Backend, backup ID or destination, schedule (New mode) or PVE parent (Attach mode), enabled flag, retention <code>KEEP_*</code> values, credentials pointers."
|
||||
},
|
||||
{
|
||||
"path": "/var/lib/proxmenux/backup-jobs/JOB_ID.paths",
|
||||
"content": "One absolute path per line — the frozen selection resolved from the profile at job-creation time. Editing the file re-runs the job with the new selection on the next timer fire."
|
||||
},
|
||||
{
|
||||
"path": "/etc/systemd/system/proxmenux-backup-JOB_ID.timer + .service",
|
||||
"content": "New mode only. The service invokes <code>run_scheduled_backup.sh JOB_ID</code>. The timer schedules it with <code>Persistent=true</code> (missed fires run at next boot) and a small <code>RandomizedDelaySec=120</code> to spread load when multiple jobs share the same OnCalendar expression."
|
||||
},
|
||||
{
|
||||
"path": "/etc/vzdump.conf script-hook",
|
||||
"content": "Attach mode only. Installed once by <code>hb_install_vzdump_hook</code>; matches every attached job's <code>PVE_STORAGE</code> against the <code>$STOREID</code> PVE passes to the hook script."
|
||||
}
|
||||
]
|
||||
},
|
||||
"runner": {
|
||||
@@ -69,11 +103,26 @@
|
||||
"heading": "Managing jobs",
|
||||
"intro": "The scheduler menu (Scripts TUI) and the Monitor Backups tab expose the same actions on any job.",
|
||||
"rows": [
|
||||
{ "action": "Run now", "detail": "Invokes the runner immediately, bypassing the timer / vzdump hook. Useful to verify a job configuration without waiting for the next scheduled fire." },
|
||||
{ "action": "Enable / Disable", "detail": "New mode: <code>systemctl enable/disable --now</code> the timer. Attach mode: flips the <code>ENABLED</code> flag in the job env — the hook script honours it on the next PVE fire." },
|
||||
{ "action": "Edit", "detail": "Reopens the destination, schedule, profile and retention prompts and rewrites the job files. Preserves the job ID." },
|
||||
{ "action": "Delete", "detail": "Removes the job env, paths file, systemd timer + service (New mode), and disables the hook binding (Attach mode). Does not touch archives already on the destination." },
|
||||
{ "action": "View log", "detail": "Streams <code>/var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log</code> — one log file per run. The runner also emits a compact status line to journald under the systemd service." }
|
||||
{
|
||||
"action": "Run now",
|
||||
"detail": "Invokes the runner immediately, bypassing the timer / vzdump hook. Useful to verify a job configuration without waiting for the next scheduled fire."
|
||||
},
|
||||
{
|
||||
"action": "Enable / Disable",
|
||||
"detail": "New mode: <code>systemctl enable/disable --now</code> the timer. Attach mode: flips the <code>ENABLED</code> flag in the job env — the hook script honours it on the next PVE fire."
|
||||
},
|
||||
{
|
||||
"action": "Edit",
|
||||
"detail": "Reopens the destination, schedule, profile and retention prompts and rewrites the job files. Preserves the job ID."
|
||||
},
|
||||
{
|
||||
"action": "Delete",
|
||||
"detail": "Removes the job env, paths file, systemd timer + service (New mode), and disables the hook binding (Attach mode). Does not touch archives already on the destination."
|
||||
},
|
||||
{
|
||||
"action": "View log",
|
||||
"detail": "Streams <code>/var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log</code> — one log file per run. The runner also emits a compact status line to journald under the systemd service."
|
||||
}
|
||||
]
|
||||
},
|
||||
"notifications": {
|
||||
@@ -83,9 +132,21 @@
|
||||
"whereNext": {
|
||||
"heading": "Where to go next",
|
||||
"items": [
|
||||
{ "label": "Creating backups", "href": "/docs/backup-restore/creating-backups", "tail": " — the interactive flow that shares the backend with the scheduler." },
|
||||
{ "label": "Destinations", "href": "/docs/backup-restore/destinations", "tail": " — per-backend configuration used by both the interactive flow and the scheduler." },
|
||||
{ "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the flow that consumes what jobs produce." }
|
||||
{
|
||||
"label": "Creating backups",
|
||||
"href": "/docs/backup-restore/creating-backups",
|
||||
"tail": " — the interactive flow that shares the backend with the scheduler."
|
||||
},
|
||||
{
|
||||
"label": "Destinations",
|
||||
"href": "/docs/backup-restore/destinations",
|
||||
"tail": " — per-backend configuration used by both the interactive flow and the scheduler."
|
||||
},
|
||||
{
|
||||
"label": "Restoring",
|
||||
"href": "/docs/backup-restore/restoring",
|
||||
"tail": " — the flow that consumes what jobs produce."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user