mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-08-01 21:36:22 +00:00
docs: promote /web from develop for backup-restore, Log2RAM and Network Flow guides
Brings 69 files from develop under /web/: - Full Backup & Restore section (11 pages EN + ES: overview, how-it-works, destinations, creating backups, scheduled jobs, restoring, cross-kernel hydration) - Log2RAM dedicated block in post-install/optional with commands + upstream link - Network Flow diagram documented on monitor/dashboard/network - Rewritten category descriptions in post-install/customizable - Fixed automated.json thresholds + link to Log2RAM section - Updated screenshots (network-flow-overview, storage-top-row, vms modals) No code, config or AppImage binaries touched — /web/ scope only. Merging deploys the documentation site to the current beta release notes.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Creating backups — interactive backup flow | ProxMenux",
|
||||
"description": "The interactive backup flow in ProxMenux. Two entry points (Scripts TUI menu and Monitor Web UI), three destinations, two profile modes, one staging step, and one confirmation dialog. Documents the six-option matrix, the default and custom profiles, and what the operator sees between selecting a backup and the archive landing on the destination.",
|
||||
"ogTitle": "ProxMenux Backup — creating backups",
|
||||
"ogDescription": "The interactive backup flow with three destinations, two profiles and a common staging step.",
|
||||
"twitterTitle": "Creating backups | ProxMenux",
|
||||
"twitterDescription": "Interactive backup flow with three destinations and two profile modes."
|
||||
},
|
||||
"header": {
|
||||
"title": "Creating backups",
|
||||
"description": "The interactive backup flow: pick a destination and a profile, review the confirmation summary, watch the archive land. Two entry points share the same backend and produce identical archives.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"intro": {
|
||||
"title": "Two entry points, identical functionality",
|
||||
"body": "The Scripts TUI and the Monitor Web UI expose <strong>exactly the same functionality</strong>. Every backup — manual or scheduled — goes through the same choice matrix (three destinations × two profiles) and invokes the same backend function per cell (<code>_bk_pbs</code>, <code>_bk_borg</code> or <code>_bk_local</code>). The archives produced from either entry point are indistinguishable. Which one to use is a matter of preference: the TUI is SSH-friendly and scriptable; the Monitor offers point-and-click and lives alongside the notification and log tail views."
|
||||
},
|
||||
"entryPoints": {
|
||||
"heading": "The two entry points",
|
||||
"rows": [
|
||||
{ "entry": "ProxMenux Scripts (TUI)", "path": "menu → Utilities → Host Config Backup", "detail": "Dialog-based flow, SSH-friendly. The main menu presents the six options directly. Uses <code>backup_menu</code> in <code>backup_host.sh</code>." },
|
||||
{ "entry": "ProxMenux Monitor (Web UI)", "path": "Backups tab → Create backup", "detail": "Wizard-style flow. Same six options presented as a two-step form (destination → profile). Same backend functions are invoked over the Flask API." }
|
||||
]
|
||||
},
|
||||
"modes": {
|
||||
"heading": "Manual vs scheduled backups",
|
||||
"body": "Backups can be produced in two modes: <strong>manual</strong> (the interactive flow this page documents — the operator picks a destination and a profile from a menu and watches the archive land) or <strong>scheduled</strong> (an unattended job that runs on a cron-style timer and applies the retention configured on the job). Both modes support the same three destinations and the same two profiles, and both are available from both entry points — the Scripts TUI menu and the Monitor Backups tab. Scheduled jobs use the same backend functions as the manual flow through <code>run_scheduled_backup.sh</code>; the archives produced are indistinguishable.",
|
||||
"seeAlso": "The scheduled-jobs page covers the full flow, including how to create a job, attach it to an existing PVE vzdump timer, and configure the retention values.",
|
||||
"monitorAlt": "ProxMenux Monitor Backups tab showing the New scheduled backup dialog with destination, profile, schedule and retention fields.",
|
||||
"monitorCaption": "Scheduled backup — ProxMenux Monitor. The same wizard-style dialog that creates a manual backup carries the schedule and retention fields at the bottom for the unattended path."
|
||||
},
|
||||
"matrix": {
|
||||
"heading": "The six-option matrix",
|
||||
"intro": "The choice determines both which backend runs and what path selection strategy is applied. Cross-reference the destination-specific pages for the configuration details of each cell.",
|
||||
"rows": [
|
||||
{ "combo": "1", "destination": "PBS", "profile": "Default", "action": "Uploads the default profile plus persistent extras to a configured PBS repository." },
|
||||
{ "combo": "2", "destination": "Borg", "profile": "Default", "action": "Creates an archive with the default profile plus persistent extras in the selected Borg repository." },
|
||||
{ "combo": "3", "destination": "Local", "profile": "Default", "action": "Writes a <code>.tar.zst</code> archive with the default profile plus persistent extras to the configured local target." },
|
||||
{ "combo": "4", "destination": "PBS", "profile": "Custom", "action": "Opens the path picker before the PBS upload; the operator ticks paths and can add new ones." },
|
||||
{ "combo": "5", "destination": "Borg", "profile": "Custom", "action": "Opens the path picker before the Borg archive create." },
|
||||
{ "combo": "6", "destination": "Local", "profile": "Custom", "action": "Opens the path picker before writing the local <code>.tar.zst</code>." }
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"heading": "Default vs Custom profile",
|
||||
"defaultTitle": "Default profile",
|
||||
"defaultBody": "The default profile is the curated list from <code>hb_default_profile_paths</code> (documented in <em>How it works</em> under <em>Path categories</em>) plus every entry in the persistent extras file <code>/usr/local/share/proxmenux/backup-extra-paths.txt</code>. The operator confirms the destination and encryption options and the backup proceeds without further path selection.",
|
||||
"customTitle": "Custom profile",
|
||||
"customBody": "The custom profile opens a checklist showing every path in the default profile (unchecked) and every persistent extra (pre-checked, prefixed with <code>[+]</code>). The operator ticks the set for this run and can press <em>Add custom path</em> to append a new absolute path. Any path added inline is persisted to <code>backup-extra-paths.txt</code> so future backups pick it up automatically without re-adding it. Removing a persistent extra unticks it for this run but does not delete it from the file — deletion is a separate <em>Manage custom paths</em> action outside the backup flow.",
|
||||
"customPickerAlt": "Custom profile checklist showing the default-profile paths (unchecked) and persistent extras (pre-checked with a [+] prefix), plus buttons to add a new path or confirm the selection.",
|
||||
"customPickerCaption": "Custom profile — the path picker. Default-profile paths are unchecked; persistent extras appear pre-checked with a [+] prefix. The operator ticks the set for this run.",
|
||||
"manageCustomAlt": "Manage custom paths menu showing the list of persistent extras and options to add, remove or edit them.",
|
||||
"manageCustomCaption": "Manage custom paths — the entry point where persistent extras are added or removed. Every path listed here is included automatically in Default-mode backups without needing to open the Custom picker."
|
||||
},
|
||||
"commonPipeline": {
|
||||
"heading": "What runs regardless of destination",
|
||||
"intro": "After the profile is resolved, every backend runs the same staging pipeline before diverging into its own upload path. <code>hb_prepare_staging</code> assembles the archive tree in <code>/tmp/proxmenux-DESTINATION-stage.XXXXXX</code> and populates each of the three payloads.",
|
||||
"steps": [
|
||||
{ "step": "1", "name": "rootfs assembly", "detail": "Runs <code>rsync -a</code> for each selected path into <code>staging_root/rootfs/</code>. Excludes volatile subpaths (bash history, caches, trash) from <code>/root/</code>. Paths absent from the source are recorded in <code>metadata/missing_paths.txt</code> without stopping the backup." },
|
||||
{ "step": "2", "name": "Manifest generation", "detail": "<code>build_manifest.sh</code> orchestrates the six collectors and writes <code>manifest.json</code> at the top of staging. On collector failure, the affected section falls back to a documented empty default; the manifest is still valid." },
|
||||
{ "step": "3", "name": "Package inventory", "detail": "<code>apt-mark showmanual</code> is captured verbatim into <code>metadata/packages.manual.list</code>. Component state is already inside the restored rootfs (<code>components_status.json</code>) because <code>/usr/local/share/proxmenux/</code> is part of the default profile." },
|
||||
{ "step": "4", "name": "Run info", "detail": "<code>metadata/run_info.env</code> records the backup run identity — hostname, timestamp, kernel version — used by the restore's compatibility check to determine the cross-kernel direction." },
|
||||
{ "step": "5", "name": "Notification (start)", "detail": "<code>hb_notify_lifecycle \"start\"</code> fires. If notifications are configured in the Monitor, an operator-facing <em>Host backup started</em> event is emitted. Silent if no channels are configured." }
|
||||
]
|
||||
},
|
||||
"included": {
|
||||
"heading": "What goes in and what stays out",
|
||||
"intro": "Every path in the resolved profile (default + persistent extras + custom-mode selection) is copied with <code>rsync -aAXH --numeric-ids</code>. A shared exclusion list applies to every path, and two directories carry additional path-specific exclusions.",
|
||||
"globalTitle": "Global exclusions (applied to every path)",
|
||||
"globalItems": [
|
||||
"<code>images/</code> — image dumps.",
|
||||
"<code>dump/</code> — vzdump outputs.",
|
||||
"<code>tmp/</code> — temporary files.",
|
||||
"<code>*.log</code> — log files."
|
||||
],
|
||||
"rootTitle": "<code>/root/</code> exclusions",
|
||||
"rootBody": "<code>/root/</code> is part of the default profile so operator scripts and config land in the archive. Volatile subpaths are dropped:",
|
||||
"rootItems": [
|
||||
"<code>.bash_history</code>",
|
||||
"<code>.cache/</code>",
|
||||
"<code>tmp/</code>",
|
||||
"<code>.local/share/Trash/</code>"
|
||||
],
|
||||
"proxmenuxTitle": "<code>/usr/local/share/proxmenux/</code> exclusions",
|
||||
"proxmenuxBody": "This directory ships user state only — <code>components_status.json</code>, preferences, post-install cache. Code that the destination will already have from its own ProxMenux install is excluded so a restore does not overwrite the target's current binaries with older ones:",
|
||||
"proxmenuxItems": [
|
||||
"<code>restore-pending/</code>, <code>scripts/</code>, <code>web/</code>",
|
||||
"<code>monitor-app/</code>, <code>monitor-app.*/</code>, <code>AppImage/</code>",
|
||||
"<code>images/</code>, <code>json/</code>",
|
||||
"<code>utils.sh</code>, <code>helpers_cache.json</code>",
|
||||
"<code>ProxMenux-Monitor.AppImage*</code>, <code>install_proxmenux*.sh</code>"
|
||||
],
|
||||
"notInProfileTitle": "Paths outside the profile",
|
||||
"notInProfileBody": "Anything not listed in <code>hb_default_profile_paths</code> and not added as a custom or persistent extra is not part of the backup. Notable examples:",
|
||||
"notInProfileItems": [
|
||||
"<strong>VM and LXC disks</strong> — handled by <code>vzdump</code>, not by this feature. Guest configuration files under <code>/etc/pve/nodes/*/qemu-server/*.conf</code> and <code>lxc/*.conf</code> are captured (they live under <code>/etc/pve</code>) so the restore reproduces the inventory; the disks themselves are re-attached from an existing vzdump/PBS backup.",
|
||||
"<strong><code>/boot</code> and <code>/boot/efi</code></strong> — kernel binaries, initramfs and the UEFI ESP partition are regenerated by the target's own <code>update-initramfs</code>, <code>update-grub</code> or <code>proxmox-boot-tool refresh</code> after the restore. The bootloader is never copied verbatim.",
|
||||
"<strong>Kernel and system runtime filesystems</strong> — <code>/proc</code>, <code>/sys</code>, <code>/dev</code> and <code>/run</code> are pseudo-filesystems produced by the kernel and udev; they are not persisted anywhere.",
|
||||
"<strong>Package binaries under <code>/usr/bin</code>, <code>/usr/lib</code>, <code>/lib</code>, <code>/sbin</code></strong> — reinstalled by the target's APT from <code>packages.manual.list</code>.",
|
||||
"<strong><code>/var/log/</code>, <code>/var/tmp/</code>, <code>/var/cache/</code></strong> — per-host runtime state, not restored.",
|
||||
"<strong><code>/home/USER</code></strong> — not in the default profile. Add it as a custom path when a system carries user home directories that must survive a restore."
|
||||
],
|
||||
"customPathsTitle": "How custom paths are handled",
|
||||
"customPathsBody": "A custom path added inline in Custom mode or persisted in <code>backup-extra-paths.txt</code> goes through the same <code>rsync</code> pipeline as default-profile paths. Global exclusions apply. If the custom path happens to be under <code>/root/</code> or <code>/usr/local/share/proxmenux/</code>, the path-specific exclusions above still apply. Every archived path — default or custom — is recorded in <code>metadata/paths_archived.txt</code>. Paths that do not exist on the source are recorded in <code>metadata/missing_paths.txt</code> without stopping the backup."
|
||||
},
|
||||
"archiveStructure": {
|
||||
"heading": "Archive structure",
|
||||
"intro": "The staging directory produced by every backend follows the same layout regardless of destination. The tarball, PBS <code>.pxar</code> or Borg archive stores this tree verbatim.",
|
||||
"tree": "backup-[timestamp]/\n├── manifest.json # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│ ├── packages.manual.list # output of apt-mark showmanual\n│ ├── run_info.env # hostname, timestamp, kernel version\n│ ├── paths_archived.txt # exact list of paths that reached rootfs/\n│ └── missing_paths.txt # paths from the profile absent on source\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, /etc/apt, ...\n ├── root/ # /root without volatile subpaths\n ├── usr/local/ # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/crontabs"
|
||||
},
|
||||
"confirmation": {
|
||||
"heading": "Confirmation summary",
|
||||
"body": "Before the backend writes anything to the destination, ProxMenux shows a summary dialog with the destination, backup ID or archive name, encryption state, and the list of paths being copied. Cancelling here aborts the backup cleanly — the staging directory is removed by the <code>trap</code> hook set on the backend function and no partial data reaches the destination."
|
||||
},
|
||||
"writing": {
|
||||
"heading": "Writing to the destination",
|
||||
"intro": "Once the operator confirms, each backend runs its own write step. The mechanics are covered in the destination pages; the shared surface is the log, the sidecar and the completion notification.",
|
||||
"rows": [
|
||||
{ "topic": "Log file", "detail": "Every backend writes its full output to <code>/tmp/proxmenux-DESTINATION-backup-YYYYMMDD_HHMMSS.log</code> and, on failure, offers to open it in a scrollable dialog. The log path is printed in the completion summary only when the file has content." },
|
||||
{ "topic": "Sidecar (local only)", "detail": "<code>hb_write_archive_sidecar</code> drops a <code>*.proxmenux.json</code> next to the local archive so the Monitor identifies it as a ProxMenux host backup even after moves or renames." },
|
||||
{ "topic": "Notification (complete/fail)", "detail": "<code>hb_notify_lifecycle \"complete\"</code> or <code>\"fail\"</code> fires with duration, archive size and — for failures — the last error-looking line from the log." }
|
||||
]
|
||||
},
|
||||
"finishedScreens": {
|
||||
"heading": "What a finished backup looks like",
|
||||
"intro": "The same completion event is surfaced by both entry points. The TUI writes a summary block to the terminal; the Monitor's Backups tab shows the run in the archive list with size, duration and status badges.",
|
||||
"scriptsAlt": "ProxMenux Scripts TUI showing a finished host backup — destination, backup ID, snapshot path, data size, duration and encryption state.",
|
||||
"scriptsCaption": "Finished backup — ProxMenux Scripts (TUI). The completion block prints the destination, backup ID, resulting snapshot or archive name, data size, duration and encryption state.",
|
||||
"monitorAlt": "ProxMenux Monitor Backups tab showing a finished host backup entry with size, duration, method badge and encryption indicator.",
|
||||
"monitorCaption": "Finished backup — ProxMenux Monitor Backups tab. The new backup appears in the archive list with the destination method badge, size, duration and — when applicable — the encryption lock indicator."
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Where to go next",
|
||||
"items": [
|
||||
{ "label": "Destinations", "href": "/docs/backup-restore/destinations", "tail": " — configuration details for Local, PBS and Borg." },
|
||||
{ "label": "Scheduled jobs", "href": "/docs/backup-restore/scheduled-jobs", "tail": " — running the same backup unattended on a schedule instead of interactively." },
|
||||
{ "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the flow that consumes what this page produces." }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Cross-kernel restore — direction detection, safe-subset filter, hydration | ProxMenux",
|
||||
"description": "How ProxMenux restores a host backup on a target running a different major kernel version. Documents how the direction of the mismatch is detected, the safe-subset filter that skips boot-critical paths when the target runs a newer kernel than the backup, and the four-phase kernel-agnostic hydration that reapplies user tuning like IOMMU, VFIO IDs and custom cmdline tokens without copying kernel-tied files verbatim.",
|
||||
"ogTitle": "ProxMenux Backup — cross-kernel restore and hydration",
|
||||
"ogDescription": "Direction-aware cross-kernel restore with a safe-subset filter and kernel-agnostic hydration.",
|
||||
"twitterTitle": "Cross-kernel restore | ProxMenux",
|
||||
"twitterDescription": "How ProxMenux handles a restore when the target kernel differs from the backup's."
|
||||
},
|
||||
"header": {
|
||||
"title": "Cross-kernel restore",
|
||||
"description": "How ProxMenux handles a restore when the target host runs a different kernel from the one recorded in the backup — especially when the target runs a newer kernel. Documents how the difference is detected, how boot-critical paths that could break the target are filtered, and how the user's own configuration is reapplied without copying kernel-tied files verbatim.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"intro": {
|
||||
"title": "Every kernel jump is handled differently",
|
||||
"body": "A Proxmox host's kernel is identified by a version string like <code>6.17.13-2-pve</code>. The first two numbers (<strong>6.17</strong> in this example) define the major version: when they change, some configuration files written for the previous kernel may not be valid on the new one. When the restore detects that the backup and the target have different major kernel versions, the flow branches based on the direction of the jump — whether the backup is older or newer than the target — because the two cases have opposite failure modes. Backups newer than the target restore cleanly as-is (bench-verified across multiple kernel jumps). Backups older than the target need a filter to avoid breaking the target's boot with configuration written for a kernel that has since changed."
|
||||
},
|
||||
"directionCheck": {
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"whyBkNewerIsSafe": {
|
||||
"heading": "Why a backup with a newer kernel than the target restores unchanged",
|
||||
"body": "When the restore runs against a target with an <em>older</em> kernel than the one recorded in the backup, every mechanism the restore depends on is independent of the kernel version. GPU driver installers and other PCI-device installers detect the running kernel with <code>uname -r</code> and compile DKMS against whatever the target has. The APT package install pulls binaries built for the target's distribution. IOMMU cmdline tokens like <code>intel_iommu=on</code> are stable across major kernel versions. The backup carries paths written under a newer kernel, but those paths (module blacklists, GRUB defaults, initramfs config) are still valid syntax on the older one — kernels ignore tokens they do not recognise instead of failing. This case is equivalent in practice to a same-kernel restore, and ProxMenux treats it as such."
|
||||
},
|
||||
"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:",
|
||||
"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." }
|
||||
],
|
||||
"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."
|
||||
},
|
||||
"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).",
|
||||
"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 operator 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 operator-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": {
|
||||
"heading": "Plan vs commit — the operator sees a preview first",
|
||||
"body": "The hydration runs in two modes. Before the confirmation dialog, ProxMenux runs <code>_rs_apply_bk_older_hydration</code> in <code>plan</code> mode: it computes exactly what would be merged, populates <code>RS_HYDRATION_SUMMARY</code> with a green block listing each action, and returns without writing. The confirmation dialog shows that green block alongside the amber safe-subset skip list, so the user sees UPFRONT what will be re-applied automatically. After the user confirms, ProxMenux re-runs the same helper in <code>commit</code> mode — same phases, same logic, but this time each phase writes to the live target. Cancelling the confirmation dialog leaves the target untouched."
|
||||
},
|
||||
"flowDiagram": {
|
||||
"heading": "The flow when the target kernel is newer than the backup's",
|
||||
"intro": "The steps added when the target kernel is newer sit inside the normal restore flow. Everything else — hot apply, pending prepare, package install, post-boot dispatcher — runs identically to a same-kernel restore.",
|
||||
"diagram": " ┌────────────────────────────────────────────────────────────┐\n │ hb_compat_check │\n │ HB_COMPAT_KERNEL_DIRECTION = bk_older │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Add 16 boot-critical paths to RS_SKIP_PATHS │\n │ /etc/default/grub, /etc/kernel, /etc/modules-load.d, ... │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"plan\" │\n │ Compute what would be merged │\n │ Populate RS_HYDRATION_SUMMARY (green block) │\n │ No writes │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Confirmation dialog │\n │ Amber: paths skipped by safe-subset filter │\n │ Green: tokens/files reapplied by hydration │\n │ User accepts or cancels │\n └───────────────────────────┬────────────────────────────────┘\n │ (accepted)\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"commit\" │\n │ Phase 1a/1b: merge cmdline tokens + GRUB keys │\n │ Phase 2: append modules to /etc/modules │\n │ Phase 3: copy whitelisted vfio/nvidia files │\n │ Set HB_HYDRATION_APPLIED=1 │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Rest of the restore runs normally │\n │ _rs_apply hot (skips RS_SKIP_PATHS) │\n │ _rs_prepare_pending_restore (writes plan.env with │\n │ HB_HYDRATION_APPLIED=1) │\n │ packages.manual.list install │\n │ Reboot │\n │ apply_pending_restore.sh (forces NEEDS_INITRAMFS=1, │\n │ NEEDS_GRUB=1 because of the hydration flag) │\n │ apply_cluster_postboot.sh │\n │ update-initramfs -u -k all │\n │ update-grub / proxmox-boot-tool refresh │\n │ component --auto-reinstall │\n └────────────────────────────────────────────────────────────┘"
|
||||
},
|
||||
"concreteExamples": {
|
||||
"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)." }
|
||||
]
|
||||
},
|
||||
"callout": {
|
||||
"warningTitle": "What hydration does not reapply when the target kernel is newer",
|
||||
"warningBody": "Hydration reapplies only the configuration ProxMenux knows to be safe across kernel versions: IOMMU tokens, VFIO IDs, whitelisted GRUB keys, and modules from a fixed list (<code>vfio*</code>, <code>nvidia*</code>, <code>i915</code>, <code>xe</code>, <code>kvm*</code>). Anything outside that set — custom initramfs hooks under <code>/etc/initramfs-tools/hooks/</code>, non-whitelisted files under <code>/etc/modprobe.d/</code>, user-authored systemd unit overrides — remains excluded. The reason is concrete: those files can call kernel-internal interfaces (module APIs, <code>/sys</code> layout, <code>udev</code> hooks) that change between major versions, and applying them verbatim over the newer kernel can prevent the target from booting. When exact reproduction of the boot chain is required, the restore must run on a host with the same major kernel version as the backup."
|
||||
},
|
||||
"codeReference": {
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"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." }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Borg destination — repository types, SSH auth, encryption | ProxMenux",
|
||||
"description": "The Borg destination writes ProxMenux host backups to a Borg repository — local, on a mounted external disk, or on a remote server accessed via SSH. Covers the borg binary resolution chain, the four SSH key strategies, repokey encryption, saved target configuration and per-scheduled-job retention.",
|
||||
"ogTitle": "ProxMenux Backup — Borg destination",
|
||||
"ogDescription": "How ProxMenux writes host backups to Borg repositories, with SSH auth strategies and repokey encryption.",
|
||||
"twitterTitle": "Borg backup destination | ProxMenux",
|
||||
"twitterDescription": "Local, USB and SSH-served Borg repositories for host backups."
|
||||
},
|
||||
"header": {
|
||||
"title": "Borg",
|
||||
"description": "The Borg destination writes host backups to a Borg repository. Three repository types are supported: local filesystem path, mounted external disk, or remote server via SSH. Chunk-level deduplication across all archives in the repository and optional repokey encryption.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"aboutBorg": {
|
||||
"heading": "What Borg is",
|
||||
"body": "Borg (also spelled BorgBackup) is an open-source deduplicating backup tool maintained by the Borg Backup community. It stores every archive as a set of variable-length chunks inside a repository; chunks are shared across archives so re-backing up unchanged data has near-zero storage cost. Borg is not tied to Proxmox — it is a general-purpose backup tool used across many environments. ProxMenux uses it as one of the three destinations for host backups; every Borg-specific mechanism described on this page (repository types, borg-serve over SSH, repokey encryption, prune) is standard Borg behaviour."
|
||||
},
|
||||
"intro": {
|
||||
"title": "Chunk-based deduplication with local or SSH-served repositories",
|
||||
"body": "A Borg repository stores chunks that are shared across every archive it contains, so re-backing up an unchanged file transfers and stores nothing new. ProxMenux invokes <code>borg create</code> against a repository at backup time and <code>borg extract</code> at restore time. The repository can live on the same filesystem as the source, on a mounted external disk, or on a remote host accessed over SSH."
|
||||
},
|
||||
"binarySourcing": {
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"serverSetup": {
|
||||
"heading": "Preparing the Borg server (server side)",
|
||||
"intro": "The <code>remote</code> repository type expects a working Borg server accessible over SSH. ProxMenux does not bootstrap the server itself — it only authorises a key against an existing account on it. This section documents what the server needs before ProxMenux can connect.",
|
||||
"hostChoicesTitle": "Where the server can live",
|
||||
"hostChoicesBody": "Any Linux host with SSH access qualifies. Common setups:",
|
||||
"hostChoicesItems": [
|
||||
"A dedicated NAS or backup box (Debian, Ubuntu, TrueNAS SCALE with a shell).",
|
||||
"An LXC container inside a Proxmox node. Small footprint, isolated from the host running the backups.",
|
||||
"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.",
|
||||
"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." }
|
||||
],
|
||||
"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."
|
||||
},
|
||||
"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:",
|
||||
"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)." }
|
||||
],
|
||||
"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:",
|
||||
"restrictLine": "command=\"/usr/bin/borg serve --restrict-to-path RPATH\",restrict PUBKEY",
|
||||
"restrictNote": "The <code>command=</code> forces every SSH session using this key to run only that borg-serve command; <code>restrict</code> disables port forwarding, agent forwarding, X11 forwarding and PTY allocation. The key cannot be used to open an interactive shell on the server or to access any other repository path, even if the account has broader privileges."
|
||||
},
|
||||
"savedTargets": {
|
||||
"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." }
|
||||
],
|
||||
"outro": "Saving is optional — the operator can decline the save prompt for a one-shot backup that leaves no credentials on the host."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"archiveFormat": {
|
||||
"heading": "Archive naming and retention",
|
||||
"intro": "Every backup creates a new archive inside the repository.",
|
||||
"namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS",
|
||||
"retentionBody": "For scheduled jobs, <code>run_scheduled_backup.sh</code> runs <code>borg prune</code> against the repository after each successful backup with the retention values configured on the job (<code>--keep-last</code>, <code>--keep-daily</code>, <code>--keep-weekly</code>). Interactive backups do not prune."
|
||||
},
|
||||
"restoreAccess": {
|
||||
"heading": "Retrieval on the restore side",
|
||||
"body": "The restore flow lists archives with <code>borg list REPO</code> and extracts the selected one with <code>borg extract REPO::ARCHIVE-NAME</code> into a staging directory that <code>_rs_check_layout</code> feeds to the standard restore pipeline. For manual retrieval outside ProxMenux the same commands work; the extracted tree is the standard three-payload layout described in <em>How it works</em>."
|
||||
},
|
||||
"references": {
|
||||
"heading": "References",
|
||||
"intro": "Official Borg documentation for the components ProxMenux relies on.",
|
||||
"items": [
|
||||
{
|
||||
"label": "Borg documentation",
|
||||
"href": "https://borgbackup.readthedocs.io/",
|
||||
"tail": " — main entry point covering concepts, deployment, quickstart and every command."
|
||||
},
|
||||
{
|
||||
"label": "borg create",
|
||||
"href": "https://borgbackup.readthedocs.io/en/stable/usage/create.html",
|
||||
"tail": " — the command ProxMenux invokes on every backup, with all supported flags."
|
||||
},
|
||||
{
|
||||
"label": "Repository encryption",
|
||||
"href": "https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-mode",
|
||||
"tail": " — the encryption modes Borg supports, including repokey which ProxMenux uses by default."
|
||||
},
|
||||
{
|
||||
"label": "borg serve and SSH deployment",
|
||||
"href": "https://borgbackup.readthedocs.io/en/stable/deployment/central-backup-server.html",
|
||||
"tail": " — how to set up a central Borg server accessed over SSH, including the borg-serve command and the restrict-to-path flag ProxMenux writes into authorized_keys."
|
||||
},
|
||||
{
|
||||
"label": "borg prune",
|
||||
"href": "https://borgbackup.readthedocs.io/en/stable/usage/prune.html",
|
||||
"tail": " — the retention model behind --keep-last / --keep-daily / --keep-weekly that ProxMenux applies per scheduled job."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Backup destinations — Local, Proxmox Backup Server, Borg | ProxMenux",
|
||||
"description": "Three storage backends receive the same ProxMenux backup payload: a local .tar.zst archive on any writeable filesystem, a PBS backup in a Proxmox Backup Server datastore, or a Borg archive in a local or remote Borg repository. Each backend has its own compression, deduplication, encryption and network characteristics.",
|
||||
"ogTitle": "ProxMenux Backup Destinations",
|
||||
"ogDescription": "Local, Proxmox Backup Server and Borg — three storage backends for the same host backup payload.",
|
||||
"twitterTitle": "ProxMenux Backup Destinations | ProxMenux",
|
||||
"twitterDescription": "Local archive, Proxmox Backup Server and Borg backends for host backups."
|
||||
},
|
||||
"header": {
|
||||
"title": "Destinations",
|
||||
"description": "Three storage backends supported by ProxMenux Backup: local archive, Proxmox Backup Server (recommended) and Borg. Each receives the same three-payload archive; they differ in storage format, deduplication, encryption model and network requirements.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"comparison": {
|
||||
"heading": "Feature comparison",
|
||||
"intro": "The following table lists the concrete differences between the three backends as they behave in ProxMenux today. All values describe observed behaviour of the backend itself, not editorial recommendations.",
|
||||
"rows": [
|
||||
{
|
||||
"feature": "Storage format",
|
||||
"local": "Single <code>.tar.zst</code> file (or <code>.tar.gz</code> if <code>zstd</code> is absent).",
|
||||
"pbs": "PBS backup (<code>.pxar</code> chunks in the datastore).",
|
||||
"borg": "Borg archive inside a Borg repository (segment files)."
|
||||
},
|
||||
{
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"feature": "Deduplication",
|
||||
"local": "None. Each backup is a full independent archive.",
|
||||
"pbs": "Full chunk-level deduplication across all backups in the datastore.",
|
||||
"borg": "Full chunk-level deduplication across all archives in the repository."
|
||||
},
|
||||
{
|
||||
"feature": "Encryption at rest",
|
||||
"local": "None (relies on filesystem-level protection).",
|
||||
"pbs": "Optional client-side keyfile encryption. Recovery blob uploaded as separate backup group when enabled.",
|
||||
"borg": "Optional repokey encryption (key stored in the repo, unlocked by a passphrase)."
|
||||
},
|
||||
{
|
||||
"feature": "Retention / pruning",
|
||||
"local": "Applied per scheduled job via <code>KEEP_LAST</code>. Old archives (and their sidecars + runner logs) are deleted symmetrically. Interactive backups do not prune.",
|
||||
"pbs": "Applied per scheduled job via <code>proxmox-backup-client prune --keep-last / --keep-daily / --keep-weekly</code>. Interactive backups do not prune.",
|
||||
"borg": "Applied per scheduled job via <code>borg prune --keep-last / --keep-daily / --keep-weekly</code>. Interactive backups do not prune."
|
||||
},
|
||||
{
|
||||
"feature": "Network",
|
||||
"local": "None. Writes to a local mount point (typically an internal disk or a USB drive).",
|
||||
"pbs": "TCP to the PBS server (default port 8007). Requires PBS credentials + fingerprint.",
|
||||
"borg": "Local filesystem path or SSH tunnel to a remote Borg host."
|
||||
},
|
||||
{
|
||||
"feature": "Dependencies",
|
||||
"local": "<code>tar</code>, <code>zstd</code> (present on Proxmox by default).",
|
||||
"pbs": "<code>proxmox-backup-client</code> package (bundled with Proxmox VE 8+).",
|
||||
"borg": "<code>borg</code> binary. Auto-provisioned by ProxMenux from the Monitor AppImage bundle when missing."
|
||||
},
|
||||
{
|
||||
"feature": "Restore access",
|
||||
"local": "Any host with tar+zstd can extract the archive. No PBS/Borg needed.",
|
||||
"pbs": "Requires <code>proxmox-backup-client</code> + PBS credentials + the keyfile (if encrypted).",
|
||||
"borg": "Requires <code>borg</code> + the repository path + the passphrase (if encrypted)."
|
||||
}
|
||||
],
|
||||
"captionCode": "feature",
|
||||
"captionLocal": "Local",
|
||||
"captionPbs": "Proxmox Backup Server (recommended)",
|
||||
"captionBorg": "Borg"
|
||||
},
|
||||
"sameArchive": {
|
||||
"heading": "The archive layout does not change with the destination",
|
||||
"body": "Inside any of the three destinations, the same <code>rootfs/</code> + <code>metadata/</code> + <code>manifest.json</code> layout described in <em>How it works</em> is present. A <code>.tar.zst</code> extracted from a local archive, a <code>.pxar</code> restored from PBS, and a Borg archive extracted with <code>borg extract</code> all yield an identical directory tree. The restore code path (<code>_rs_check_layout</code>, <code>_rs_apply</code>, <code>_rs_prepare_pending_restore</code>) reads the same three payloads without knowing which destination they came from."
|
||||
},
|
||||
"extractStandalone": {
|
||||
"heading": "Extracting a backup outside of ProxMenux",
|
||||
"intro": "Any of the three archive formats can be read with standard tools without ProxMenux installed on the reading host. The commands below produce the same directory tree that a restore consumes internally.",
|
||||
"localCmd": "# From a local .tar.zst archive:\ntar --zstd -xf hostcfg-HOST-TIMESTAMP.tar.zst\n\n# From a .tar.gz fallback:\ntar -xzf hostcfg-HOST-TIMESTAMP.tar.gz",
|
||||
"pbsCmd": "# From a PBS backup (requires proxmox-backup-client):\nproxmox-backup-client restore \\\n --repository USER@REALM@HOST:DATASTORE \\\n host/hostcfg-HOST/BACKUP-TIME \\\n hostcfg.pxar /tmp/hostcfg\n\n# Add --keyfile KEY-PATH when the backup is encrypted.",
|
||||
"borgCmd": "# From a Borg archive (requires borg binary + passphrase for encrypted repos):\nborg extract REPO-PATH::ARCHIVE-NAME\n\n# On a remote SSH-served repo:\nborg extract ssh://USER@HOST:PORT/REPO-PATH::ARCHIVE-NAME",
|
||||
"note": "The extracted tree can then be inspected manually or fed to a manual restore. See the individual destination pages for the exact retrieval flow ProxMenux uses under the hood."
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Per-destination detail",
|
||||
"intro": "Each destination has its own page covering the configuration flow, the on-disk format and the retrieval command used by the restore.",
|
||||
"items": [
|
||||
{
|
||||
"label": "Local archive",
|
||||
"href": "/docs/backup-restore/destinations/local",
|
||||
"tail": " — pre-configuring a local target, USB drive mounting, the safety check against writing the archive into itself, the sidecar JSON that lets the Monitor identify the file."
|
||||
},
|
||||
{
|
||||
"label": "Proxmox Backup Server (recommended)",
|
||||
"href": "/docs/backup-restore/destinations/pbs",
|
||||
"tail": " — datastore selection, credentials and fingerprint, encryption keyfile lifecycle, recovery passphrase, uploading the recovery blob to PBS for fresh-install recovery."
|
||||
},
|
||||
{
|
||||
"label": "Borg",
|
||||
"href": "/docs/backup-restore/destinations/borg",
|
||||
"tail": " — local vs. SSH-served repositories, the Borg binary sourcing (system / cache / Monitor AppImage bundle / GitHub download), repository initialization, passphrase handling, SSH key setup for remote repos."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Local archive destination — tar.zst on a filesystem or USB drive | ProxMenux",
|
||||
"description": "The local backup destination writes a single .tar.zst archive to any writeable directory: an internal disk, a Proxmox mount point, an NFS share, or a USB drive. Documents the configuration flow, the USB detection and mounting logic, the safety check against writing the archive into a path being backed up, and the sidecar JSON that identifies the file.",
|
||||
"ogTitle": "ProxMenux Backup — Local archive destination",
|
||||
"ogDescription": "How ProxMenux writes local backups as .tar.zst archives and how to configure the destination directory or USB drive.",
|
||||
"twitterTitle": "Local backup destination | ProxMenux",
|
||||
"twitterDescription": "How ProxMenux writes local backups as .tar.zst archives on a filesystem or USB drive."
|
||||
},
|
||||
"header": {
|
||||
"title": "Local archive",
|
||||
"description": "The local destination writes a single compressed tar archive to any writeable directory on the host: an internal disk, an NFS or SMB mount, or a USB drive.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"intro": {
|
||||
"title": "One file, self-contained",
|
||||
"body": "A local backup produces a single <code>hostcfg-HOST-TIMESTAMP.tar.zst</code> file (or <code>.tar.gz</code> when <code>zstd</code> is absent). The file contains the entire archive tree — <code>manifest.json</code>, <code>metadata/</code> and <code>rootfs/</code> — and can be restored on any Proxmox host with the ProxMenux restore flow, or extracted manually with <code>tar --zstd -xf</code> on any Linux system. No server, no repository init, no external dependency. This is the destination with the shortest recovery path when neither PBS nor Borg is available."
|
||||
},
|
||||
"targetConfig": {
|
||||
"heading": "Configuring the target directory",
|
||||
"intro": "The local destination is a <strong>single persisted target directory</strong> — not a list. ProxMenux stores the operator's choice at <code>/usr/local/share/proxmenux/local-target.conf</code> and reads it on every backup. When no target has been configured, the default <code>HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump</code> is used (the same directory Proxmox uses for its own <code>vzdump</code> outputs). The target is configured from <em>Configure backup destinations → Local destinations</em>:",
|
||||
"options": [
|
||||
"<strong>Use default (<code>/var/lib/vz/dump</code>).</strong> The Proxmox local storage. Present on every Proxmox install; the archive sits next to vzdump outputs and is picked up by the Monitor's Backups tab automatically.",
|
||||
"<strong>Enter a custom path.</strong> Any absolute filesystem path can be used: an NFS mount, an SMB share mounted via <code>fstab</code>, a dedicated ZFS dataset, a second internal disk. ProxMenux validates that the path exists and is a directory before persisting it.",
|
||||
"<strong>Pick a USB drive.</strong> Opens the USB submenu (below), which detects removable devices and offers to mount or format one."
|
||||
]
|
||||
},
|
||||
"usbFlow": {
|
||||
"heading": "USB drive detection and mounting",
|
||||
"intro": "The USB submenu lists partitions on removable devices reported by <code>lsblk</code>. Each partition is presented with its size, filesystem label and current state. The state determines the action ProxMenux offers.",
|
||||
"statesTitle": "The three device states",
|
||||
"stateRows": [
|
||||
{ "state": "mounted", "shown": "Size · label · [fstype] · → /mount/point", "action": "The partition is already mounted somewhere. Selecting it persists the current mount point as the local target. No mounting is performed." },
|
||||
{ "state": "unmounted", "shown": "Size · label · [fstype] · (not mounted — will be mounted)", "action": "A filesystem is present but not mounted. On confirmation, ProxMenux runs <code>hb_mount_usb_partition</code>: creates <code>/mnt/backup-LABEL</code> (or a UUID-based path if there is no label), mounts the partition and persists the mount point as the local target." },
|
||||
{ "state": "empty", "shown": "Size · raw USB disk — no filesystem (will be FORMATTED)", "action": "The device has no filesystem. A destructive path — protected by two confirmations. First a Yes/No dialog explains that the operation will erase the disk. Second, an inputbox requires the operator to <strong>type the exact device path</strong> (e.g. <code>/dev/sdb</code>) before ProxMenux creates a fresh GPT + ext4 partition and mounts it." }
|
||||
],
|
||||
"notMountedFallback": "When no USB device is detected, the submenu falls back to a plain inputbox. The operator can enter an arbitrary mount point path; if the path is not a registered mount point, a confirmation dialog warns before proceeding."
|
||||
},
|
||||
"safetyCheck": {
|
||||
"heading": "Safety check — destination inside a backup path",
|
||||
"body": "Before writing the archive, <code>_bk_local</code> verifies that the destination directory is <strong>not</strong> a subpath of any directory being backed up. A common footgun would be adding <code>/root</code> to the profile and picking <code>/root/backups</code> as the destination — the archive would then include itself, either producing a corrupt archive or growing without bound until the disk fills. The check resolves both paths with <code>readlink -m</code>, compares them and, on conflict, aborts the backup with a dialog that names the conflicting path and lists three ways to resolve it: choose a destination outside the conflicting path, remove the custom entry that contains the destination, or use Custom mode to uncheck the conflicting path for this run."
|
||||
},
|
||||
"archiveFormat": {
|
||||
"heading": "Archive format and compression",
|
||||
"intro": "The output filename embeds the source hostname and the backup timestamp so that a directory holding several archives sorts chronologically and each file is self-identifying.",
|
||||
"namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS.tar.zst",
|
||||
"compressionTitle": "Compression",
|
||||
"compressionBody": "The primary path uses <code>tar --zstd -cf</code> — a single-command pipeline that compresses at zstd's default level. When <code>zstd</code> is not present on the source (rare on Proxmox but possible on minimal installs), ProxMenux falls back to <code>gzip</code>. In the fallback path, if <code>pv</code> is available, a progress bar is added to the pipeline so the operator sees the archive size grow in real time; without <code>pv</code>, plain <code>tar -czf</code> is used silently.",
|
||||
"sourceTitle": "What goes into the archive",
|
||||
"sourceBody": "The tar command is invoked with <code>-C \"$staging_root\" .</code>, which archives the <strong>full staging root</strong>: <code>rootfs/</code>, <code>metadata/</code> and <code>manifest.json</code>. All three payloads land side by side at the top of the tarball. Extracting the archive produces the exact same tree that the restore code consumes."
|
||||
},
|
||||
"sidecar": {
|
||||
"heading": "The sidecar JSON",
|
||||
"intro": "Every successful local backup produces a companion file: <code>HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json</code>, written next to the archive by <code>hb_write_archive_sidecar</code>. This small JSON file lets the ProxMenux Monitor identify the archive as a ProxMenux host backup even if it is later moved, renamed or archived elsewhere.",
|
||||
"contentTitle": "Sidecar contents",
|
||||
"contentBody": "The sidecar stores the schema version, whether the backup came from an interactive run or a scheduled job (<code>kind</code>), the job ID for scheduled runs, the profile mode used (<code>default</code> or <code>custom</code>), the source hostname, the original archive basename, an ISO-8601 creation timestamp and the archive size in bytes.",
|
||||
"whyBody": "The Monitor's Backups tab scans configured local directories for <code>*.proxmenux.json</code> sidecars — not for <code>*.tar.zst</code> files — because a <code>.tar.zst</code> without a sidecar might not be a ProxMenux backup at all. The scan is fast (JSON files are tiny) and the pairing is stable across renames of the archive as long as the sidecar is renamed to match."
|
||||
},
|
||||
"restoreAccess": {
|
||||
"heading": "Restoring from a local archive",
|
||||
"body": "The ProxMenux restore flow discovers local archives by scanning the configured local target for sidecars and displaying them in the Archives list. Selecting one triggers <code>_rs_check_layout</code>, which extracts the tarball into a staging directory and confirms the three-payload layout before proceeding. For manual extraction outside of ProxMenux, <code>tar --zstd -xf hostcfg-HOSTNAME-TIMESTAMP.tar.zst -C /tmp/hostcfg</code> yields the same tree the restore code consumes."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Proxmox Backup Server destination — repository, encryption, recovery | ProxMenux",
|
||||
"description": "The PBS destination writes ProxMenux host backups as PBS backups. Documents repository auto-discovery from /etc/pve/storage.cfg, manual PBS configuration, the .pxar upload command, the client-side keyfile encryption model, the recovery passphrase escrow blob, and the fresh-install keyfile recovery from PBS.",
|
||||
"ogTitle": "ProxMenux Backup — Proxmox Backup Server destination",
|
||||
"ogDescription": "How ProxMenux writes host backups to Proxmox Backup Server with client-side keyfile encryption and recovery escrow.",
|
||||
"twitterTitle": "PBS backup destination | ProxMenux",
|
||||
"twitterDescription": "Proxmox Backup Server destination with keyfile encryption and recovery passphrase escrow."
|
||||
},
|
||||
"header": {
|
||||
"title": "Proxmox Backup Server",
|
||||
"description": "The PBS destination uploads the staging root as a single .pxar backup to a Proxmox Backup Server datastore, with optional client-side keyfile encryption and an automatic recovery passphrase escrow for fresh-install disaster recovery.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"recommendedBadge": "Recommended destination",
|
||||
"aboutPbs": {
|
||||
"heading": "What Proxmox Backup Server is",
|
||||
"body": "Proxmox Backup Server (PBS) is Proxmox's own backup server product, developed and maintained by the same team that authors Proxmox VE. It is a dedicated backup server designed to receive backups from Proxmox VE hosts (VMs, LXCs and — via <code>proxmox-backup-client</code> — arbitrary host directories) with chunk-based deduplication, client-side encryption, and retention policies applied server-side. ProxMenux uses PBS as one of the three destinations for host backups; every PBS-specific mechanism described on this page (backup groups, <code>.pxar</code> archives, <code>--backup-id</code>, keyfile encryption) is standard PBS behaviour."
|
||||
},
|
||||
"intro": {
|
||||
"title": "One backup per run, chunk-level dedup",
|
||||
"body": "A PBS backup produces a single backup entry in the datastore, grouped under the backup ID <code>host/hostcfg-HOSTNAME/BACKUP-TIME</code>. The payload is a <code>.pxar</code> archive containing the same three-block layout described in <em>How it works</em>. PBS deduplicates at the chunk level across all backups in the datastore, so subsequent backups of the same host transfer and store only the chunks that changed. Retention is applied by ProxMenux itself for scheduled jobs — <code>run_scheduled_backup.sh</code> runs <code>proxmox-backup-client prune</code> with <code>--keep-last</code> / <code>--keep-daily</code> / <code>--keep-weekly</code> after each successful run using the values configured on the job."
|
||||
},
|
||||
"repoSelection": {
|
||||
"heading": "Repository selection",
|
||||
"intro": "ProxMenux discovers PBS repositories from two sources on every backup. The operator picks one from a unified menu; the choice determines <code>HB_PBS_REPOSITORY</code>, <code>HB_PBS_SECRET</code> and <code>HB_PBS_FINGERPRINT</code> for the run.",
|
||||
"sourceRows": [
|
||||
{
|
||||
"source": "Proxmox storage.cfg (auto-discovered)",
|
||||
"path": "/etc/pve/storage.cfg + /etc/pve/priv/storage/NAME.pw",
|
||||
"content": "Any <code>pbs:</code> stanza in Proxmox's own storage config is picked up automatically. Server, datastore, username and fingerprint come from the stanza. The password is read from Proxmox's own credentials directory. No re-entry needed on the ProxMenux side — the repository is available as soon as it is configured in Proxmox."
|
||||
},
|
||||
{
|
||||
"source": "ProxMenux manual config",
|
||||
"path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
|
||||
"content": "Added from <em>Configure backup destinations → PBS destinations → Add PBS</em>. Prompts for a name, username (<code>root@pam</code> or <code>user@pbs!token</code>), host or IP, datastore and password. The password re-prompts on empty input — an empty save would otherwise persist silently and every subsequent backup would fail with an opaque authentication error. This path is used when the target PBS is not registered as Proxmox storage."
|
||||
}
|
||||
],
|
||||
"menuTitle": "Selection menu",
|
||||
"menuBody": "Both sources are shown in a single menu, each row tagged with its origin (<code>[proxmox]</code> or <code>[manual]</code>). Entries whose password could not be resolved are tagged with a <code>⚠ no password</code> warning — selecting one triggers a password re-entry before the backup starts. The fingerprint is passed to <code>proxmox-backup-client</code> via the <code>PBS_FINGERPRINT</code> environment variable; when absent, the client asks the operator to accept the server certificate interactively on the first backup."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"pxarTitle": "Why the source is the full staging root",
|
||||
"pxarBody": "The <code>.pxar</code> source is the entire <code>staging_root</code> — <code>rootfs/</code>, <code>metadata/</code> and <code>manifest.json</code> together. Earlier versions passed <code>$staging_root/rootfs</code> as the source; that left <code>metadata/</code> out of the archive and the restore's compatibility check had nothing to read, degrading to cross-host warnings even on same-host restores. Old backups created with the rootfs-only source still restore correctly via <code>_rs_check_layout</code>'s case-3 branch, which wraps a flat <code>etc/var/root/usr</code> tree back into a <code>rootfs/</code> hierarchy."
|
||||
},
|
||||
"encryption": {
|
||||
"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": "On first use, <code>proxmox-backup-client key create --kdf none</code> generates the keyfile at <code>/usr/local/share/proxmenux/pbs-key.conf</code> (<code>chmod 600</code>). Subsequent backups reuse it after a single confirmation dialog. If key creation fails, the backup is cancelled and the tool's error output is shown in a dialog.",
|
||||
"recoveryTitle": "Recovery passphrase and escrow blob",
|
||||
"recoveryBody": "After the keyfile is created, ProxMenux prompts twice for a recovery passphrase (with match validation) and runs <code>openssl</code> to produce <code>pbs-key.recovery.enc</code> — the keyfile encrypted with the passphrase. A copy is written to <code>/root/pbs-key.recovery-HOSTNAME-YYYYMMDD.enc</code> for offsite storage. Cancelling the passphrase dialog wipes the freshly-created keyfile.",
|
||||
"blobUploadTitle": "Paired backup group on PBS",
|
||||
"blobUploadBody1": "After a PBS backup that used the keyfile, the escrow blob is uploaded as a second backup group: <code>host/hostcfg-HOSTNAME-keyrecovery/BACKUP-TIME</code>. The shared <code>hostcfg-HOSTNAME</code> prefix places both groups adjacent in the PBS UI; the <code>-keyrecovery</code> suffix labels the relationship. The upload runs without <code>--keyfile</code> (the blob is already passphrase-protected by openssl) and only when the current backup used the keyfile.",
|
||||
"blobUploadConstraintTitle": "Why two groups",
|
||||
"blobUploadConstraintBody": "<code>--keyfile</code> is a per-invocation flag in <code>proxmox-backup-client backup</code>: all archives in a single invocation are encrypted with the keyfile or none are. <code>hostcfg.pxar</code> requires encryption; <code>keyrecovery.conf</code> cannot be encrypted with the same keyfile (fresh-install recovery would then require the keyfile it is meant to recover). Two invocations, two backup IDs.",
|
||||
"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 escrow blob.",
|
||||
"recoverTitle": "Fresh-install recovery",
|
||||
"recoverBody": "On a host without a local keyfile, the restore flow calls <code>hb_pbs_try_keyfile_recovery</code>. The function lists keyrecovery groups on the configured PBS, downloads the newest and prompts for the passphrase. On success, <code>pbs-key.conf</code> is written to the ProxMenux state directory and the encrypted backup can be restored. Without both the keyfile and the passphrase, the encrypted backup is not recoverable."
|
||||
},
|
||||
"restoreAccess": {
|
||||
"heading": "Retrieval on the restore side",
|
||||
"body": "The restore flow discovers ProxMenux host backups on PBS by listing backup groups under the configured repository and filtering by backup ID pattern. The Monitor's Backups tab renders the same list. Selecting a backup triggers <code>proxmox-backup-client restore</code> with the same repository + password + fingerprint (and <code>--keyfile</code> when the backup was encrypted), extracting the <code>.pxar</code> into a staging directory that <code>_rs_check_layout</code> then feeds to the standard restore pipeline. For manual retrieval outside ProxMenux, the same command extracts the archive to any path — the resulting tree can be inspected or fed to a hand-driven restore."
|
||||
},
|
||||
"references": {
|
||||
"heading": "References",
|
||||
"intro": "Official Proxmox Backup Server documentation for the components ProxMenux relies on.",
|
||||
"items": [
|
||||
{
|
||||
"label": "Proxmox Backup Server documentation",
|
||||
"href": "https://pbs.proxmox.com/docs/",
|
||||
"tail": " — main entry point covering installation, administration, storage, users and roles."
|
||||
},
|
||||
{
|
||||
"label": "proxmox-backup-client",
|
||||
"href": "https://pbs.proxmox.com/docs/backup-client.html",
|
||||
"tail": " — the command-line tool ProxMenux invokes for every backup and restore. Covers backup IDs, backup types, archives, repository syntax and environment variables."
|
||||
},
|
||||
{
|
||||
"label": "Client-side encryption",
|
||||
"href": "https://pbs.proxmox.com/docs/backup-client.html#encryption",
|
||||
"tail": " — keyfile creation, --kdf modes, the encryption model that ProxMenux extends with a passphrase escrow."
|
||||
},
|
||||
{
|
||||
"label": "Datastore management",
|
||||
"href": "https://pbs.proxmox.com/docs/storage.html",
|
||||
"tail": " — creating and managing the datastores that receive host backups, including chunk-store layout and permissions."
|
||||
},
|
||||
{
|
||||
"label": "Pruning and garbage collection",
|
||||
"href": "https://pbs.proxmox.com/docs/maintenance.html#pruning",
|
||||
"tail": " — the retention model behind --keep-last / --keep-daily / --keep-weekly that ProxMenux applies per scheduled job, plus how PBS reclaims chunks after prune."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "How ProxMenux Backup works internally — rootfs, manifest, applications",
|
||||
"description": "Detailed breakdown of what a ProxMenux backup contains: the rootfs produced by rsync of the default path profile, the structured manifest built by six independent collectors, and the application inventory that drives the automatic reinstall of packages and components after a restore.",
|
||||
"ogTitle": "How ProxMenux Backup works internally",
|
||||
"ogDescription": "The three payloads of a ProxMenux backup explained: rootfs, manifest and applications.",
|
||||
"twitterTitle": "How ProxMenux Backup works | ProxMenux",
|
||||
"twitterDescription": "The three payloads of a ProxMenux backup and how the restore reproduces the source host from them."
|
||||
},
|
||||
"header": {
|
||||
"title": "How it works",
|
||||
"description": "The internal breakdown of a ProxMenux backup — filesystem, manifest and application inventory — and how the restore consumes all three to reproduce the source host on a target that may not share the same kernel.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"layout": {
|
||||
"heading": "Archive layout",
|
||||
"intro": "Every archive follows the same tree layout regardless of destination. The <code>metadata/</code> subdirectory holds the structured payloads; the <code>rootfs/</code> subdirectory holds the filesystem copy.",
|
||||
"treeCaption": "The staging directory laid out during a backup. Every destination receives the same tree (adapted to its native format: tar for local, PBS chunks for PBS, borg segments for Borg).",
|
||||
"tree": "backup-[timestamp]/\n├── manifest.json # structured host state\n├── metadata/\n│ ├── packages.manual.list # apt-mark showmanual\n│ ├── run_info.env # backup run identity + kernel version\n│ ├── paths_archived.txt # exact list of paths that reached rootfs/\n│ └── missing_paths.txt # paths from the profile absent on source\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, …\n ├── root/ # /root (with volatile subdirs excluded)\n ├── usr/local/ # /usr/local/bin, /usr/local/share/proxmenux, …\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/…"
|
||||
},
|
||||
"rootfs": {
|
||||
"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 operator 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:",
|
||||
"categoriesTitle": "Path categories",
|
||||
"categoryRows": [
|
||||
{
|
||||
"category": "PVE core",
|
||||
"paths": "/etc/pve, /var/lib/pve-cluster, /etc/vzdump.conf",
|
||||
"why": "Cluster filesystem contents, cluster live data, vzdump defaults."
|
||||
},
|
||||
{
|
||||
"category": "Host identity & network",
|
||||
"paths": "/etc/hostname, /etc/hosts, /etc/timezone, /etc/resolv.conf, /etc/network",
|
||||
"why": "Everything the host needs to come up on the network with the same identity."
|
||||
},
|
||||
{
|
||||
"category": "Access & auth",
|
||||
"paths": "/etc/ssh, /etc/sudoers, /etc/sudoers.d, /etc/pam.d, /etc/security",
|
||||
"why": "SSH keys, sudo rules and PAM configuration. Losing these locks the operator out of the restored host."
|
||||
},
|
||||
{
|
||||
"category": "Kernel & boot",
|
||||
"paths": "/etc/default/grub, /etc/kernel, /etc/modules, /etc/modules-load.d, /etc/modprobe.d, /etc/sysctl.conf, /etc/sysctl.d, /etc/udev/rules.d, /etc/fstab, /etc/iscsi, /etc/multipath",
|
||||
"why": "IOMMU tokens, module blacklists, VFIO device IDs, mount table, storage stack config."
|
||||
},
|
||||
{
|
||||
"category": "Shell & locale",
|
||||
"paths": "/etc/environment, /etc/bash.bashrc, /etc/inputrc, /etc/profile, /etc/profile.d, /etc/locale.gen, /etc/locale.conf",
|
||||
"why": "System-wide shell setup, environment variables, locale generation configuration."
|
||||
},
|
||||
{
|
||||
"category": "Packaging & cron",
|
||||
"paths": "/etc/apt, /etc/cron.d, /etc/cron.{daily,hourly,weekly,monthly}, /etc/cron.allow, /etc/cron.deny, /var/spool/cron/crontabs",
|
||||
"why": "APT sources for consistent package resolution, scheduled tasks defined by the operator."
|
||||
},
|
||||
{
|
||||
"category": "ProxMenux state & tools",
|
||||
"paths": "/etc/proxmenux, /etc/systemd/system, /etc/log2ram.conf, /etc/logrotate.conf, /etc/logrotate.d, /etc/lm-sensors, /etc/sensors3.conf, /etc/fail2ban, /etc/snmp, /etc/postfix, /etc/wireguard, /etc/openvpn, /etc/grafana, /etc/influxdb, /etc/prometheus, /etc/telegraf, /etc/zabbix",
|
||||
"why": "Optional but common Proxmox tooling. Missing paths are noted in <code>metadata/missing_paths.txt</code> without stopping the backup."
|
||||
},
|
||||
{
|
||||
"category": "ProxMenux binaries & root",
|
||||
"paths": "/usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux, /root (volatile subdirs excluded)",
|
||||
"why": "ProxMenux-installed binaries and per-user configuration under <code>/root</code>. Volatile paths (<code>.bash_history</code>, <code>.cache/</code>, <code>tmp/</code>, <code>.local/share/Trash/</code>) are excluded from the copy."
|
||||
},
|
||||
{
|
||||
"category": "ZFS state (conditional)",
|
||||
"paths": "/etc/zfs",
|
||||
"why": "Only included when the source host runs ZFS. Contains <code>zpool.cache</code> and <code>hostid</code>."
|
||||
}
|
||||
],
|
||||
"customTitle": "Extending the profile with custom paths",
|
||||
"customBody": "Beyond the default profile, ProxMenux offers two ways to include additional paths in a backup. They compose without conflict and both apply to interactive backups and scheduled jobs.",
|
||||
"customExtrasTitle": "1. Persistent extras (per-host file)",
|
||||
"customExtrasBody": "A text file at <code>/usr/local/share/proxmenux/backup-extra-paths.txt</code> holds a list of absolute paths the operator has marked as \"always include\" on this host. When a backup runs in <strong>Default</strong> mode, ProxMenux appends these paths to the default profile automatically without asking. The file is edited from the interface — no manual editing needed — and persists across reboots and updates. One absolute path per line; <code>#</code> comments are allowed.",
|
||||
"customModeTitle": "2. Custom mode (per-run)",
|
||||
"customModeBody": "Launching a backup in <strong>Custom</strong> mode replaces the automatic application of the default profile with a checklist showing every path: the default-profile entries and the persistent extras (prefixed with <code>[+]</code> and pre-checked). The operator ticks or unticks entries for that specific run, and can also press <em>Add custom path</em> to enter a new path — which is then persisted to the extras file for future backups.",
|
||||
"customMissingTitle": "Paths absent from the source",
|
||||
"customMissingBody": "Any path from the profile (default or added) that does not exist on the source host is recorded in <code>metadata/missing_paths.txt</code> inside the archive. The backup neither fails nor stops — the operator sees a summary of archived paths and missing paths at the end. In practice this happens for optional tooling paths like <code>/etc/wireguard</code> or <code>/etc/prometheus</code> when those tools are not installed."
|
||||
},
|
||||
"manifest": {
|
||||
"heading": "The manifest payload",
|
||||
"intro": "<code>manifest.json</code> is a structured JSON document that describes the source host at the moment of the backup. It is produced by <strong>six independent collectors</strong> orchestrated by <code>build_manifest.sh</code>. Each collector is read-only, produces a well-defined JSON fragment, and falls back to a safe empty default if it fails — the manifest is still usable if one section is incomplete.",
|
||||
"orchestratorCaption": "The six collectors compose the manifest. Each collector runs in its own subprocess; a failure in one falls back to a documented safe default and warns but does not abort the backup.",
|
||||
"collectorRows": [
|
||||
{
|
||||
"collector": "collect_source_host.sh",
|
||||
"produces": "source_host",
|
||||
"content": "Hostname, PVE version (<code>pveversion</code>), PBS version if the host runs the backup-server role, kernel (<code>uname -r</code>), boot mode (efi/bios), root filesystem type, CPU model + architecture, memory in KB."
|
||||
},
|
||||
{
|
||||
"collector": "collect_hardware.sh",
|
||||
"produces": "hardware_inventory",
|
||||
"content": "GPUs (with vendor and ProxMenux installer mapping), TPUs (Coral USB + M.2 with <code>lsusb</code>/<code>lspci</code> detection), NICs (with MAC, PCI slot and bridge membership), wireless devices. GPU entries carry a <code>passthrough_eligible</code> heuristic."
|
||||
},
|
||||
{
|
||||
"collector": "collect_storage.sh",
|
||||
"produces": "storage_inventory",
|
||||
"content": "ZFS pools (with pool type + member disks resolved to <code>/dev/disk/by-id/*</code> for portability), LVM volume groups + thin pools, physical disks with SMART capability, PVE <code>storage.cfg</code> entries, external mounts."
|
||||
},
|
||||
{
|
||||
"collector": "collect_kernel.sh",
|
||||
"produces": "kernel_params",
|
||||
"content": "Operator-authored tokens from <code>/proc/cmdline</code> (stripped of boilerplate like <code>BOOT_IMAGE=</code>, <code>root=</code>, <code>ro/rw</code>, <code>quiet</code>, <code>splash</code>), modules loaded at boot from <code>/etc/modules</code>, and paths of <code>/etc/modprobe.d/*.conf</code> files with actual directives (<code>options</code>, <code>blacklist</code>, <code>install</code>, <code>alias</code>, <code>softdep</code>)."
|
||||
},
|
||||
{
|
||||
"collector": "collect_proxmenux_state.sh",
|
||||
"produces": "proxmenux_installed_components",
|
||||
"content": "Reads <code>/usr/local/share/proxmenux/managed_installs.json</code> (registry of everything ProxMenux has installed) and <code>installed_tools.json</code>. Each entry keeps the installer path (<code>menu_script</code>) so the restore can trigger the exact same install flow."
|
||||
},
|
||||
{
|
||||
"collector": "collect_guests.sh",
|
||||
"produces": "vms_lxcs_at_backup",
|
||||
"content": "Enumerates VMs (<code>qm list</code>) and LXCs (<code>pct list</code>) present at backup time — VMID, name, current status. Only the inventory: the actual guest data is the responsibility of <code>vzdump</code> / PBS."
|
||||
}
|
||||
],
|
||||
"schemaTitle": "Schema validation",
|
||||
"schemaBody": "The manifest validates against <code>scripts/backup_restore/schema/manifest.schema.json</code>. Running <code>build_manifest.sh --validate</code> triggers a Python-side JSON Schema validation (requires <code>python3</code> + <code>jsonschema</code>). If the module is not present, the check is skipped silently — validation is primarily a developer aid, not a runtime dependency."
|
||||
},
|
||||
"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.",
|
||||
"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)",
|
||||
"componentsBody": "A JSON registry under <code>/usr/local/share/proxmenux/</code> that records every component ProxMenux has installed with its exact state: version, ProxMenux-specific flags (for NVIDIA: <code>patched</code> boolean; for Coral: DKMS version). This file lives inside the rootfs — not in <code>metadata/</code> — because it is <em>read after</em> the rootfs has been copied to the target. The post-boot dispatcher (<code>apply_cluster_postboot.sh</code>) iterates over its entries and runs each component's <code>--auto-reinstall</code> hook, which reads the recorded state and reproduces the install against the target's current kernel.",
|
||||
"componentInstallersTitle": "Component installers",
|
||||
"componentInstallersBody": "Four ProxMenux installers currently expose a <code>--auto-reinstall</code> entry point:",
|
||||
"installerRows": [
|
||||
{
|
||||
"component": "nvidia_driver",
|
||||
"installer": "gpu_tpu/nvidia_installer.sh",
|
||||
"action": "Reads <code>version</code> + <code>patched</code>. Downloads the exact same NVIDIA runfile, builds DKMS modules against the target's kernel, re-applies the ProxMenux patch if the source had it."
|
||||
},
|
||||
{
|
||||
"component": "coral_driver",
|
||||
"installer": "gpu_tpu/install_coral.sh",
|
||||
"action": "Reads the Coral driver version. Compiles the DKMS module against the target's kernel."
|
||||
},
|
||||
{
|
||||
"component": "amdgpu_top",
|
||||
"installer": "gpu_tpu/amd_gpu_tools.sh",
|
||||
"action": "Reads the recorded version. Re-downloads the exact <code>.deb</code> from the GitHub release."
|
||||
},
|
||||
{
|
||||
"component": "intel_gpu_tools",
|
||||
"installer": "gpu_tpu/intel_gpu_tools.sh",
|
||||
"action": "APT-installs the package. Idempotent if already present from <code>packages.manual.list</code>."
|
||||
}
|
||||
]
|
||||
},
|
||||
"restoreFlow": {
|
||||
"heading": "How the restore consumes the three payloads",
|
||||
"intro": "The restore is a five-stage pipeline. Each stage reads a specific subset of the archive and updates the target host. No stage requires the source host to be reachable — the archive is fully self-contained.",
|
||||
"stagesCaption": "Stage 1 uses the manifest to decide what to touch. Stage 2 copies rootfs paths that are safe to apply on a running system. Stage 3 stages the risky paths for the next boot. Stage 4 handles packages. Stage 5 runs after the reboot and reinstalls components against the target's kernel.",
|
||||
"stageRows": [
|
||||
{
|
||||
"stage": "1",
|
||||
"name": "Compatibility check",
|
||||
"reads": "manifest.json",
|
||||
"action": "Runs <code>hb_compat_check</code>. Compares source vs. target hardware (NICs, storage IDs), PVE version, and major kernel version. Sets <code>HB_COMPAT_KERNEL_DIRECTION</code> (<code>same</code>, <code>bk_newer</code> or <code>bk_older</code>) and populates <code>RS_SKIP_PATHS</code> with hardware-drift and cross-kernel exclusions."
|
||||
},
|
||||
{
|
||||
"stage": "2",
|
||||
"name": "Hot apply",
|
||||
"reads": "rootfs/ (safe paths only)",
|
||||
"action": "<code>_rs_apply … hot</code> copies <code>hb_classify_path</code>=<code>hot</code> entries directly to the live target. Anything under <code>/etc/pve</code>, <code>/etc/network</code> or paths classified as <em>reboot</em>/<em>dangerous</em> is deferred."
|
||||
},
|
||||
{
|
||||
"stage": "3",
|
||||
"name": "Pending prepare",
|
||||
"reads": "rootfs/ (reboot + dangerous paths)",
|
||||
"action": "<code>_rs_prepare_pending_restore</code> stages risky paths under <code>/var/lib/proxmenux/pending-restore/</code>, writes <code>plan.env</code>, <code>apply-on-boot.list</code> and <code>rs-skip-paths.txt</code>, and enables <code>proxmenux-restore-onboot.service</code> to fire on the next boot."
|
||||
},
|
||||
{
|
||||
"stage": "4",
|
||||
"name": "Package install",
|
||||
"reads": "metadata/packages.manual.list",
|
||||
"action": "<code>_rs_run_complete_extras</code> runs the cascade-safe filter and calls <code>apt-get install -y</code> with the surviving package list. Full output goes to <code>/var/log/proxmenux/restore-apt-*.log</code>."
|
||||
},
|
||||
{
|
||||
"stage": "5",
|
||||
"name": "Post-boot",
|
||||
"reads": "rootfs (already applied) + components_status.json",
|
||||
"action": "After reboot, <code>apply_pending_restore.sh</code> plays back the deferred paths and <code>apply_cluster_postboot.sh</code> runs <code>update-initramfs</code>, <code>update-grub</code> (or <code>proxmox-boot-tool refresh</code>) and iterates over <code>components_status.json</code> firing each component's <code>--auto-reinstall</code> hook."
|
||||
}
|
||||
]
|
||||
},
|
||||
"whyItWorks": {
|
||||
"heading": "Why the three-payload split is the right one",
|
||||
"body": "The split is not a filesystem decision — it is a <strong>lifecycle</strong> decision. Filesystem content moves with <code>rsync</code>: fast, transparent, atomic per file. Configuration state that a restore has to interpret before touching the target moves as <strong>structured JSON</strong>: readable independently, versionable through a schema, machine-diffable against the target's own state. Software that has to be re-installed against the target's environment moves as an <strong>inventory</strong>: names and versions only, letting the target's package manager and ProxMenux's own installers decide the actual binaries. Each payload optimises for what it needs to do, and the three combine into a restore that is atomic in intent but fault-tolerant in practice: a corrupt manifest still leaves the rootfs restorable, a missing package still leaves the components installable, a component installer failing on one entry does not stop the next."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "ProxMenux Backup & Restore — Overview | Full host backup and restore for Proxmox VE",
|
||||
"description": "ProxMenux Backup & Restore captures the complete state of a Proxmox host — filesystem, structured configuration manifest, and installed packages/components — and reproduces it on the same or a different host. The backup and the restore are self-contained: no external dependencies, and cross-kernel restores are supported through a direction-aware safe-subset filter and kernel-agnostic hydration.",
|
||||
"ogTitle": "ProxMenux Backup & Restore — Overview",
|
||||
"ogDescription": "Full host backup and restore for Proxmox VE with structured manifest, package list and component reinstallers.",
|
||||
"twitterTitle": "ProxMenux Backup & Restore | ProxMenux",
|
||||
"twitterDescription": "Full host backup and restore for Proxmox VE with structured manifest, package list and component reinstallers."
|
||||
},
|
||||
"header": {
|
||||
"title": "Backup & Restore",
|
||||
"description": "Full-host backup and restore for Proxmox VE. Captures filesystem, configuration and installed components in a single archive, and reproduces the host on the same or a different Proxmox install with no external dependencies.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"intro": {
|
||||
"title": "What it is, in one paragraph",
|
||||
"body": "A ProxMenux backup captures the complete state of a Proxmox host: <strong>the filesystem</strong> (relevant directories under <code>/etc</code>, <code>/root</code>, <code>/var/lib/pve-cluster</code>, plus optional custom paths), <strong>a structured manifest</strong> (JSON with the detected hardware, kernel parameters, network layout, ZFS state, users and cron entries), and <strong>an application inventory</strong> (all packages marked as manually installed by APT, plus the list of components installed by ProxMenux with their exact versions). Any of the three supported destinations — local archive, Proxmox Backup Server (recommended) or Borg — receives the same self-contained payload, and any of them can hydrate the target host without depending on the source being reachable at restore time."
|
||||
},
|
||||
"whatItIsNot": {
|
||||
"heading": "What it is not",
|
||||
"intro": "The section covers <strong>host-level</strong> backup and restore: the Proxmox installation itself, not the workloads running on top of it.",
|
||||
"items": [
|
||||
"<strong>It is not a VM/CT backup tool.</strong> Guest disks and RAM state are not captured by this feature; that is what <code>vzdump</code> is for. The guest <strong>configuration files</strong> (<code>/etc/pve/nodes/<node>/qemu-server/*.conf</code> and <code>lxc/*.conf</code>) <strong>are</strong> captured, so after a restore the guest inventory reappears and disks can be re-attached from an existing PBS/local backup.",
|
||||
"<strong>It is not a cluster-wide operation.</strong> Each node backs up itself. Cluster membership is captured as part of <code>/etc/pve</code> so a restored node can be re-added to its cluster, but restoring a full cluster requires per-node coordination.",
|
||||
"<strong>It is not a full disk image.</strong> Kernel binaries, the initramfs and the boot partition are not captured. On a restore, ProxMenux relies on the target host's own boot artifacts (regenerated automatically by <code>update-initramfs</code> and the bootloader tool) and installs matching drivers against the target's running kernel."
|
||||
]
|
||||
},
|
||||
"threePillars": {
|
||||
"heading": "The three pillars of a backup",
|
||||
"intro": "A ProxMenux archive is structured around three self-contained payloads. The restore uses all three together to reproduce the source host on a target that may not even have the same kernel installed.",
|
||||
"diagramCaption": "Every backup ships the same three payloads regardless of destination. The restore consumes all three: rootfs to lay down files, manifest to detect drift and cross-kernel differences, and application inventory to re-install packages and components against the target's own kernel.",
|
||||
"pillar1Label": "Filesystem",
|
||||
"pillar1Detail": "rootfs/\n(rsync of\n/etc, /root,\n/var/lib/pve-cluster,\n+ optional paths)",
|
||||
"pillar2Label": "Manifest",
|
||||
"pillar2Detail": "manifest.json\n(hardware, kernel\nparams, network,\nZFS, users, cron,\nZFS pools, storage)",
|
||||
"pillar3Label": "Applications",
|
||||
"pillar3Detail": "packages.manual.list\n+ components_status.json\n(APT manual + ProxMenux\ninstallers with versions)"
|
||||
},
|
||||
"restoreIsUniversal": {
|
||||
"heading": "The restore reproduces the source host, not the archive",
|
||||
"body": "Restoring a ProxMenux backup does not just extract the filesystem. The restore flow reads the manifest to detect drift between the source and the target (hardware differences, NIC renames, kernel version, ZFS pool identity), replays the filesystem, then triggers the correct component installer for every service that was installed on the source (NVIDIA driver, Coral TPU, AMD GPU tools, Intel GPU tools). Each installer runs against the <strong>target's current kernel</strong>, so the restored host does not depend on the source's kernel being present. When the target's kernel is newer than the backup's, a kernel-agnostic <strong>hydration</strong> pass merges the operator's own tuning (IOMMU tokens, VFIO device IDs, custom quirks, GRUB keys) into the target's fresh boot configuration without copying kernel-tied files verbatim."
|
||||
},
|
||||
"twoInterfaces": {
|
||||
"heading": "Two interfaces, one backend",
|
||||
"intro": "Every step of the backup and restore workflow is available from two entry points. Both call the same shell library, produce identical archives, and read the same job registry.",
|
||||
"cliLabel": "ProxMenux Scripts (TUI)",
|
||||
"cliDetail": "menu → Utilities →\nHost Backup / Restore\n\nDialog-based flow,\nSSH-friendly, scriptable\nfor unattended use.",
|
||||
"webLabel": "ProxMenux Monitor (Web UI)",
|
||||
"webDetail": "Backups tab in the\nMonitor's Web interface.\n\nOne-click restore, live\nlog tail, integrated with\nnotifications and the\nrollback delta viewer."
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Where to go from here",
|
||||
"intro": "Each subsection below covers one aspect of the workflow in depth. Start with <em>How it works</em> if the goal is to understand what the archive contains and why. Jump to <em>Destinations</em> to configure the target for the copy. Read <em>Restoring</em> and <em>Cross-kernel restore</em> for the recovery side.",
|
||||
"items": [
|
||||
{
|
||||
"label": "How it works",
|
||||
"href": "/docs/backup-restore/how-it-works",
|
||||
"tail": " — the three payloads (rootfs, manifest, applications) in detail, with the collectors that produce them and the format of each file."
|
||||
},
|
||||
{
|
||||
"label": "Destinations",
|
||||
"href": "/docs/backup-restore/destinations",
|
||||
"tail": " — comparison of local, Proxmox Backup Server (recommended) and Borg, and how to configure each one."
|
||||
},
|
||||
{
|
||||
"label": "Creating backups",
|
||||
"href": "/docs/backup-restore/creating-backups",
|
||||
"tail": " — one-off backups, the default path profile, adding custom paths, encryption for PBS."
|
||||
},
|
||||
{
|
||||
"label": "Scheduled jobs",
|
||||
"href": "/docs/backup-restore/scheduled-jobs",
|
||||
"tail": " — new jobs vs. attaching to an existing PVE vzdump job, scheduling formats, the job detail modal."
|
||||
},
|
||||
{
|
||||
"label": "Restoring",
|
||||
"href": "/docs/backup-restore/restoring",
|
||||
"tail": " — the three actions on an archive (view, download, restore), Complete vs. Custom restore, the post-boot dispatcher and why the last ten minutes matter."
|
||||
},
|
||||
{
|
||||
"label": "Cross-kernel restore",
|
||||
"href": "/docs/backup-restore/cross-kernel",
|
||||
"tail": " — direction-aware behaviour when the target kernel differs from the backup's, the safe-subset filter and the four hydration phases."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Scheduled jobs — timers, attach mode, retention | ProxMenux",
|
||||
"description": "Scheduled ProxMenux host backup jobs. Two creation modes (own systemd timer or attach to an existing PVE vzdump job), retention values applied per job via proxmox-backup-client/borg/local prune, storage layout under /var/lib/proxmenux/backup-jobs, and the runner script that produces archives identical to the interactive flow.",
|
||||
"ogTitle": "ProxMenux Backup — scheduled jobs",
|
||||
"ogDescription": "Unattended scheduled host backups with attach mode, retention, and the same three destinations as the interactive flow.",
|
||||
"twitterTitle": "Scheduled jobs | ProxMenux",
|
||||
"twitterDescription": "Unattended scheduled host backup jobs with attach mode and retention."
|
||||
},
|
||||
"header": {
|
||||
"title": "Scheduled jobs",
|
||||
"description": "Unattended host backup jobs. Two creation models: a new independent job with its own schedule, or a job attached to an existing PVE vzdump task that inherits that task's schedule and retention. Both produce archives identical to the interactive flow.",
|
||||
"section": "Backup & Restore"
|
||||
},
|
||||
"intro": {
|
||||
"title": "The two scheduled-job models",
|
||||
"body": "A scheduled job can be created following one of two models:",
|
||||
"modelsList": [
|
||||
"<strong>New independent job</strong> — ProxMenux defines the job schedule itself with its own systemd timer, independent of any other task on the host. Compatible with all three destinations: Local, PBS and Borg.",
|
||||
"<strong>Attach to an existing PVE vzdump task</strong> — the job has no schedule of its own; it runs automatically whenever the parent PVE vzdump task that already backs up the VMs and LXCs on this host fires. Inherits the schedule and retention from the parent task. Compatible with Local and PBS (Borg is not supported because PVE has no native scheduler for Borg)."
|
||||
]
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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>)." }
|
||||
]
|
||||
},
|
||||
"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.",
|
||||
"steps": [
|
||||
{ "step": "1", "detail": "During job creation, ProxMenux lists compatible parent PVE tasks via <code>hb_pve_list_vzdump_jobs_for_backend</code>. The operator 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." }
|
||||
],
|
||||
"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."
|
||||
},
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"runner": {
|
||||
"heading": "What a job does when it fires",
|
||||
"intro": "Every job — whether new-model or attached — runs the same three-step sequence:",
|
||||
"steps": [
|
||||
"<strong>Prepare the backup.</strong> Reads the job configuration (destination, profile, paths) and assembles the archive tree exactly the same way the interactive flow does. The result is the same archive a manual backup with those settings would produce.",
|
||||
"<strong>Write to the destination.</strong> Uploads or writes the archive to the configured destination — a local <code>.tar.zst</code> file, a PBS backup, or a Borg archive — using exactly the same tools and credentials a manual backup to the same destination would use.",
|
||||
"<strong>Apply retention.</strong> Removes old backups from the destination, keeping the <code>keep-last</code> / <code>keep-daily</code> / <code>keep-weekly</code> counts configured on the job. Pruning is performed by the destination itself: PBS applies its own retention, Borg runs <code>borg prune</code>, and for Local ProxMenux deletes the files that fall outside the <code>keep-last</code> window."
|
||||
]
|
||||
},
|
||||
"management": {
|
||||
"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." }
|
||||
]
|
||||
},
|
||||
"notifications": {
|
||||
"heading": "Notifications",
|
||||
"body": "Every scheduled run fires the same <code>hb_notify_lifecycle</code> events as the interactive flow (<code>start</code>, <code>complete</code>, <code>fail</code>). If notification channels are configured in the Monitor, unattended jobs surface their results the same way manual backups do — an operator does not need to check the log to know whether a job succeeded."
|
||||
},
|
||||
"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." }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
"title": "Before you start",
|
||||
"drivers": "<strong>Coral drivers already installed on the host</strong>. This script does not install them; it only configures passthrough to the container. Run <hostLink>Install Coral TPU on the Host</hostLink> first if you haven't.",
|
||||
"driversCheck": "ls /dev/apex_* 2>/dev/null ; lsusb | grep -E '1a6e:089a|18d1:9302'",
|
||||
"container": "<strong>An existing LXC container</strong>, ideally running a <strong>Debian / Ubuntu</strong>-based distro. The inside-container install uses <code>apt-get</code>; Alpine / Arch containers are not currently supported by this script.",
|
||||
"container": "<strong>An existing LXC container</strong>, ideally running a <strong>Debian / Ubuntu</strong>-based distro — the in-container runtime install uses <code>apt-get</code>. <strong>Non-Debian containers</strong> (Alpine, Arch, RHEL, SUSE…) are still supported in <strong>passthrough-only mode</strong>: the script detects the distro and offers a prompt to skip the <code>libedgetpu</code> APT install while still writing the device passthrough config — useful for app containers that bundle the runtime themselves (e.g. the Frigate Docker image).",
|
||||
"downtime": "<strong>Be OK with a brief downtime</strong> of the container. The script stops it to apply config changes, then starts it back up to install drivers inside. No host reboot needed."
|
||||
},
|
||||
"hostPrep": {
|
||||
@@ -70,8 +70,8 @@
|
||||
],
|
||||
"noIgpuTitle": "Why no iGPU drivers here?",
|
||||
"noIgpuBody": "Earlier versions of this script also installed Intel <code>va-driver-all</code>, <code>intel-opencl-icd</code> and friends so the same container could do Quick Sync video decode alongside Coral inference. That doubled-up responsibility caused confusing failures when the user only wanted Coral. The iGPU side is now the exclusive job of <lxcGpuLink>Add GPU to LXC</lxcGpuLink> — run it first if you also want hardware video decode in the container.",
|
||||
"debianTitle": "Debian / Ubuntu containers only",
|
||||
"debianBody": "The in-container install uses <code>apt-get</code> directly. Alpine, Arch or RHEL-based containers are not currently supported — the install step will fail and leave the LXC with the passthrough config but no drivers inside. For those distros, install the Coral runtime manually following Google's <coralLink>official guide</coralLink> after the LXC config step."
|
||||
"debianTitle": "Non-Debian containers — passthrough-only mode",
|
||||
"debianBody": "The in-container runtime install uses <code>apt-get</code>, which only ships on Debian/Ubuntu-family distros. On <strong>Alpine, Arch, RHEL or SUSE</strong> containers the script detects the distro via <code>/etc/os-release</code> and shows a confirmation prompt: <em>continue in passthrough-only mode</em> (writes the device config to <code>/etc/pve/lxc/<ctid>.conf</code> and skips the <code>libedgetpu</code> APT install) or <em>abort</em>. Passthrough-only is the right choice if the app container that will actually use Coral already bundles the runtime — the canonical example is the Frigate Docker image. Otherwise, follow Google's <coralLink>official guide</coralLink> to install <code>libedgetpu</code> manually after the script has written the LXC config."
|
||||
},
|
||||
"summary": {
|
||||
"title": "Summary",
|
||||
@@ -95,8 +95,8 @@
|
||||
"apexBody": "Host apex module isn't loaded. On the host: <code>lsmod | grep apex</code> — if empty, run <code>modprobe apex</code>, or reboot if you just installed Coral drivers. Once the host has <code>/dev/apex_0</code>, restart the container: <code>pct stop <ctid> && pct start <ctid></code>.",
|
||||
"replugTitle": "USB Coral disappears after replug in a different port",
|
||||
"replugBody": "This is exactly why the script mounts <code>/dev/bus/usb</code> instead of the <code>/dev/coral</code> symlink. If you're hitting this, check your LXC config has <code>lxc.mount.entry: /dev/bus/usb dev/bus/usb ...</code> and not a reference to <code>/dev/coral</code> directly. Old configs from earlier script versions may need updating — re-run the script on the same container and the config gets refreshed.",
|
||||
"alpineTitle": "In-container install fails on an Alpine container",
|
||||
"alpineBody": "The script uses <code>apt-get</code>, which Alpine doesn't have. The LXC passthrough config is still valid — just install the Coral runtime manually with <code>apk add</code> following Google's guide for Alpine, or use a Debian-based container if you don't need the smaller footprint.",
|
||||
"alpineTitle": "Alpine / Arch / RHEL / SUSE container — runtime not installed",
|
||||
"alpineBody": "If you chose <em>passthrough-only mode</em> when the script prompted, the LXC config is written and the Coral device is visible inside the container, but the <code>libedgetpu</code> runtime is not installed. That's by design: Google's APT repo only ships for Debian/Ubuntu. Install the runtime manually with your distro's package manager (Alpine: <code>apk add</code>; Arch: AUR; RHEL/SUSE: build from source) following Google's official guide, or run an app container that bundles the runtime — the Frigate Docker image is the canonical example: just expose the device with <code>--device /dev/apex_0:/dev/apex_0</code> (M.2) or the USB bind mount the script already wrote (USB).",
|
||||
"frigateTitle": "Frigate says 'Coral EdgeTPU detected but not available'",
|
||||
"frigateBody": "Almost always a permissions issue inside the container. Frigate runs as root by default; check the root user is in the <code>plugdev</code> group inside the container (for USB), and that the process can read <code>/dev/apex_0</code> (for M.2). <code>ls -l /dev/apex_0</code> from inside the container should show group <code>apex</code> — if not, add the GID alignment to <code>/etc/group</code> or switch the container to privileged mode.",
|
||||
"logsTitle": "Check both host and container logs",
|
||||
|
||||
@@ -35,6 +35,32 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"flow": {
|
||||
"heading": "Network Flow diagram",
|
||||
"intro": "Between the top row and the three group cards, the tab renders a live topology view called <strong>Network Flow</strong>. It draws every path a packet can take through the host — physical NICs at one end, bridges in the middle, VMs and containers at the other — with animated pulses that show real-time rx/tx traffic on each link.",
|
||||
"imageAlt": "Network Flow diagram — NICs on the left, host and bridges in the middle, VMs and LXCs on the right, with animated pulses on each connection",
|
||||
"imageCaption": "Network Flow — colour-coded nodes plus animated comets whose direction and thickness reflect live traffic.",
|
||||
"elementsTitle": "What the diagram shows",
|
||||
"elementsIntro": "Every node is one entity from the same inventory used by the group cards below, but arranged as a topology so relationships between them are immediate:",
|
||||
"elements": [
|
||||
"<strong>NICs</strong> (amber) — every physical interface with link up. Interfaces reported as down are drawn faded.",
|
||||
"<strong>Host</strong> (amber) — the Proxmox host itself, sitting between the NICs and the bridges. Rendered as a fixed anchor; not clickable.",
|
||||
"<strong>Bridges</strong> (cyan) — Linux and OVS bridges. Only bridges that carry at least one active guest are drawn; unused bridges are hidden to keep the diagram focused on what actually moves traffic.",
|
||||
"<strong>LXCs</strong> (cyan) — running containers attached to a bridge.",
|
||||
"<strong>VMs</strong> (purple) — running virtual machines attached to a bridge.",
|
||||
"Offline guests (stopped VMs / containers) are hidden."
|
||||
],
|
||||
"pulsesTitle": "What the animation encodes",
|
||||
"pulsesBody": "The animated comets that travel along each edge represent live traffic:",
|
||||
"pulses": [
|
||||
"<strong>Direction</strong> — a pulse flowing toward the NIC is <em>tx</em> from the guest; toward the guest is <em>rx</em>.",
|
||||
"<strong>Stroke width</strong> scales with the guest's combined rx+tx rate. An idle guest still draws a faint animated line; a busy one gets a thick one.",
|
||||
"<strong>Comet head glow</strong> — warm at ~1 MB/s, hot at ≥30 MB/s. Useful to spot at a glance which guest is dominating a NIC.",
|
||||
"<strong>Click</strong> — every node except the host is clickable and opens the same per-interface drill-in modal documented below. Tapping the host is intentionally a no-op — there is no host-level modal in this view."
|
||||
],
|
||||
"useTitle": "When it's useful",
|
||||
"useBody": "The group cards below tell you <em>what</em> exists; the diagram tells you <em>how it's connected</em> and where the traffic is flowing right now. Patterns easy to spot at a glance: a bridge with no active guest attached, two heavy guests going through the same NIC (bottleneck candidate), or a single VM saturating a link while everything else is quiet."
|
||||
},
|
||||
"groups": {
|
||||
"heading": "Three interface groups",
|
||||
"intro": "Below the top row, three cards split the inventory by role. Each card has its own active-count badge in the header. Interface <strong>type</strong> is identified at a glance by a coloured badge on every row:",
|
||||
|
||||
@@ -53,6 +53,30 @@
|
||||
"sparklineTitle": "The sparkline is meaningful",
|
||||
"sparklineBody": "The temperature card draws a 5-minute trace under the value, with the line and gradient colour following the same Warning/Critical pair documented above. It's the fastest way to see whether the host is in a thermal climb without opening the detail modal."
|
||||
},
|
||||
"processes": {
|
||||
"heading": "Top processes by CPU / Memory",
|
||||
"intro": "The CPU Usage and Memory cards on the Overview tab are clickable. Clicking either opens a sortable list of the top 25 processes — ordered by CPU usage when opened from the CPU card, by resident memory when opened from the Memory card.",
|
||||
"listTitle": "The list dialog",
|
||||
"listItems": [
|
||||
"<strong>Auto-refresh</strong> — the list updates every 5 seconds while the dialog is open and stops polling as soon as it closes.",
|
||||
"<strong>Filter</strong> — the search box narrows the list by command, user or PID.",
|
||||
"<strong>Inline bar</strong> — the primary metric column draws a small bar scaled to the highest value in the filtered list, so ranking stays visible even when no process is near 100 %.",
|
||||
"<strong>Mobile layout</strong> — under 640 px the PID and User columns hide so Command, CPU % and Memory still fit on a phone screen without horizontal scroll."
|
||||
],
|
||||
"captureListAlt": "Top processes by Memory modal — table with PID, USER, COMMAND, CPU %, Memory columns sorted by RSS",
|
||||
"captureListCaption": "The Memory card opens the list sorted by RSS (indigo accent). The CPU card opens the same list sorted by CPU usage (blue accent).",
|
||||
"detailTitle": "Per-process detail",
|
||||
"detailIntro": "Clicking any row in the list opens a second dialog with the live picture of that one process, organised in four sections:",
|
||||
"detailItems": [
|
||||
"<strong>Overview</strong> — state, parent process, thread count, open file descriptors, user and group.",
|
||||
"<strong>Resources</strong> — CPU %, Memory %, Resident (RSS), Virtual size, Swap, I/O read and write totals.",
|
||||
"<strong>Command</strong> — short name, full command line, executable path and working directory.",
|
||||
"<strong>Lifetime</strong> — start timestamp and elapsed runtime."
|
||||
],
|
||||
"detailRefresh": "The detail dialog refreshes every 3 seconds while open. If the process finishes mid-dialog, polling stops, an amber <em>This process has finished</em> banner appears, and the last captured snapshot stays on screen (dimmed) so you can still see what was happening just before it ended.",
|
||||
"captureDetailAlt": "Process detail modal — Overview, Resources, Command and Lifetime sections for a single PID",
|
||||
"captureDetailCaption": "Per-process detail dialog opened from a list row. The accent colour matches the card that opened it (blue for CPU, indigo for Memory)."
|
||||
},
|
||||
"middle": {
|
||||
"heading": "Middle: node metrics charts",
|
||||
"body1": "Below the top row sits the <code>NodeMetricsCharts</code> component — historical CPU, memory and disk-I/O graphs sourced from Proxmox's own RRD store via <code>/api/node/metrics</code>. A timeframe selector switches between <em>1 hour / 24 hours / 7 days / 30 days / 1 year</em>; data resolution drops as the window grows so the chart stays smooth.",
|
||||
|
||||
@@ -79,13 +79,13 @@
|
||||
},
|
||||
{
|
||||
"tool": "Log2RAM (SSD-aware, auto)",
|
||||
"what": "Detects whether the root disk is SSD / NVMe and installs Log2RAM from upstream git. Sizes the ramdisk by host RAM (128M / 256M / 512M), schedules periodic sync and a 95 % threshold auto-sync. Adjusts journald limits to fit in the ramdisk.",
|
||||
"what": "Reduces SSD/NVMe wear by moving /var/log to a tmpfs ramdisk with periodic disk sync. Installed from the upstream project when the root disk is SSD or NVMe. Sizes the ramdisk by host RAM (128M / 256M / 512M), schedules periodic sync and an auto-sync guard that vacuums at 80% and truncates hot logs at 92% before writing. Adjusts journald limits to fit in the ramdisk. Full details on <link>Log2RAM</link>.",
|
||||
"category": "Storage",
|
||||
"categorySlug": "storage"
|
||||
},
|
||||
{
|
||||
"tool": "ZFS autotrim (SSD-only)",
|
||||
"what": "Enables zpool autotrim=on on every ZFS pool whose vdevs are all SSD/NVMe with TRIM support (checks /sys/block/<dev>/queue/rotational and discard_granularity). Pools backed by HDDs are skipped automatically. Only pools actually changed by ProxMenux are recorded for uninstall — pools you set autotrim on manually are left alone.",
|
||||
"what": "Enables zpool autotrim=on on every ZFS pool whose vdevs are all SSD/NVMe with TRIM support (checks /sys/block/[dev]/queue/rotational and discard_granularity). Pools backed by HDDs are skipped automatically. Only pools actually changed by ProxMenux are recorded for uninstall — pools you set autotrim on manually are left alone.",
|
||||
"category": "Storage",
|
||||
"categorySlug": "storage"
|
||||
},
|
||||
|
||||
@@ -25,43 +25,43 @@
|
||||
"categories": [
|
||||
{
|
||||
"name": "Basic Settings",
|
||||
"description": "Repositories, system upgrade, timezone, locale, common utilities."
|
||||
"description": "Clean the APT sources, run the official Proxmox upgrade and set timezone, locale and common utilities. The baseline every other group sits on top of."
|
||||
},
|
||||
{
|
||||
"name": "System",
|
||||
"description": "Journald, logrotate, kernel limits, memory tuning, kernel panic, fast reboots."
|
||||
"description": "Tune the log subsystem and kernel limits for a host that carries many VMs and containers. Covers journald, logrotate, sysctl (memory, file limits), kernel panic behaviour and fast reboots."
|
||||
},
|
||||
{
|
||||
"name": "Virtualization",
|
||||
"description": "Guest agent auto-install, IOMMU/VFIO enablement for PCI passthrough."
|
||||
"description": "Prepare the host for advanced virtualization. Auto-installs the guest agent on templates and enables IOMMU/VFIO for PCI passthrough of GPUs, controllers and TPUs."
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"description": "APT over IPv4, network sysctl tuning, Open vSwitch, TCP BBR, persistent interface names."
|
||||
"description": "Harden and tune the host's network stack. Forces APT over IPv4, applies sysctl hardening + TCP buffer tuning, offers Open vSwitch and BBR, and pins persistent interface names by MAC."
|
||||
},
|
||||
{
|
||||
"name": "Storage",
|
||||
"description": "ZFS ARC sizing, ZFS auto-snapshot, vzdump backup speed limits."
|
||||
"description": "Set up Proxmox's common storage subsystems: ZFS ARC sizing, ZFS auto-snapshot, and vzdump speed limits to avoid saturating the disk during backups."
|
||||
},
|
||||
{
|
||||
"name": "Security",
|
||||
"description": "Disable portmapper/rpcbind to reduce the attack surface."
|
||||
"description": "Reduce the attack surface exposed by default. Disables the RPC services (portmapper/rpcbind) that Proxmox does not need but leaves listening."
|
||||
},
|
||||
{
|
||||
"name": "Customization",
|
||||
"description": "Bashrc colors & aliases, MOTD banner, subscription-notice removal."
|
||||
"description": "Change the host's visual and shell experience: colors and aliases in bashrc, MOTD banner and removal of the subscription notice in the web UI."
|
||||
},
|
||||
{
|
||||
"name": "Monitoring",
|
||||
"description": "OVH Real-Time Monitoring (only on detected OVH servers)."
|
||||
"description": "Integrate the host with OVH Real-Time Monitoring. Only appears when ProxMenux detects an OVH server."
|
||||
},
|
||||
{
|
||||
"name": "Performance",
|
||||
"description": "Parallel gzip (pigz) for faster compression in backups and transfers."
|
||||
"description": "Speed up compression operations (backups, transfers) by replacing gzip with its parallel counterpart pigz."
|
||||
},
|
||||
{
|
||||
"name": "Optional",
|
||||
"description": "AMD CPU fixes, Fastfetch, Figurine, Ceph repo, High Availability, Log2RAM."
|
||||
"description": "Niche pieces not every host needs: AMD CPU fixes, Fastfetch banner, Figurine 3D hostname, Ceph repository, High Availability services and Log2RAM to reduce SSD wear."
|
||||
}
|
||||
],
|
||||
"mixTip": {
|
||||
|
||||
@@ -136,6 +136,27 @@
|
||||
"automates": "This adjustment automates the following process:",
|
||||
"outro": "After installation, you'll see your hostname displayed in 3D ASCII art each time you log in, making it immediately clear which Proxmox node you're working on."
|
||||
},
|
||||
"log2ram": {
|
||||
"title": "Install Log2RAM (SSD/NVMe wear reduction)",
|
||||
"intro": "Log2RAM mounts <code>/var/log</code> on a tmpfs ramdisk and periodically syncs the contents back to the underlying disk. On a hypervisor journald churn hits the root SSD every few seconds; moving the writes to RAM reduces wear and disk I/O without losing logs — the periodic sync flushes them to disk and a clean shutdown flushes them too.",
|
||||
"upstreamLabel": "Upstream project:",
|
||||
"upstreamUrl": "https://github.com/azlux/log2ram",
|
||||
"upstreamLinkLabel": "azlux/log2ram on GitHub",
|
||||
"doesLabel": "What ProxMenux does:",
|
||||
"doesItems": [
|
||||
"Detects whether the root disk is SSD/NVMe by reading <code>/sys/block/<dev>/queue/rotational</code>. On a rotational disk the Automated flow asks first before installing.",
|
||||
"Clones the upstream repository (<code>azlux/log2ram</code>) into <code>/tmp/log2ram</code> and runs its <code>install.sh</code>, then enables the <code>log2ram</code> systemd unit.",
|
||||
"Sizes the ramdisk based on host RAM: <code>≤ 8 GB → 128M</code>, <code>≤ 16 GB → 256M</code>, <code>> 16 GB → 512M</code>. Writes the value to <code>SIZE=</code> in <code>/etc/log2ram.conf</code>.",
|
||||
"Schedules a periodic disk sync via <code>/etc/cron.d/log2ram</code>: every 1h / 3h / 6h according to the same RAM tier.",
|
||||
"Installs an auto-sync guard at <code>/usr/local/bin/log2ram-check.sh</code>, wired to <code>/etc/cron.d/log2ram-auto-sync</code> to run every 10 minutes. When <code>/var/log</code> reaches 80% of the ramdisk size the guard vacuums journald; at 92% it also truncates <code>pveproxy access/error</code> and <code>pveam.log</code> before syncing — the plain <code>log2ram write</code> command copies tmpfs to disk but does NOT shrink the tmpfs, so this guard prevents PVE from crashing with <em>No space left on device</em> when logs grow uncontrolled.",
|
||||
"Adjusts systemd-journald limits (<code>SystemMaxUse</code>, <code>RuntimeMaxUse</code>) to fit within the ramdisk so a single burst cannot fill it.",
|
||||
"Registers itself in <code>installed_tools.json</code> so it can be reverted from Uninstall Optimizations."
|
||||
],
|
||||
"howUseLabel": "How to use:",
|
||||
"howUseBody": "On the Automated flow Log2RAM applies unattended when an SSD/NVMe root is detected. On the Customizable flow the script prompts for size, sync interval and whether to enable the 90% auto-sync guard.",
|
||||
"verifyLabel": "Verify and manage from the shell:",
|
||||
"verifyCode": "# Service state and configured timers\nsystemctl status log2ram\nsystemctl list-timers log2ram*\n\n# Current ramdisk usage — /var/log IS the tmpfs itself\ndf -h /var/log\n\n# Force a sync now (tmpfs → disk); does NOT shrink the tmpfs\nlog2ram write\n\n# Trigger the auto-sync guard on demand (vacuum + sync when usage is high)\n/usr/local/bin/log2ram-check.sh\n\n# Current config: SIZE, mail settings, path\ncat /etc/log2ram.conf\n\n# ProxMenux-specific cron jobs\ncat /etc/cron.d/log2ram /etc/cron.d/log2ram-auto-sync\n\n# Recent Log2RAM activity\njournalctl -u log2ram -n 50 --no-pager"
|
||||
},
|
||||
"autoApplication": {
|
||||
"title": "Automatic Application",
|
||||
"body": "These optional features are applied only when specifically selected during the post-install process. Each feature can be individually chosen based on your specific needs and preferences."
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"intro": {
|
||||
"title": "What this category covers",
|
||||
"body": "Three storage-related optimizations: tune the <strong>ZFS ARC</strong> cache size to a sensible fraction of host RAM, install and schedule <strong>ZFS auto-snapshots</strong>, and remove throttles from <strong>vzdump</strong> so backups run at full speed. All three are independent — pick the ones that match your setup."
|
||||
"body": "Three storage-related optimizations: tune the <strong>ZFS ARC</strong> cache size to a sensible fraction of host RAM, install and schedule <strong>ZFS auto-snapshots</strong>, and remove throttles from <strong>vzdump</strong> so backups run at full speed. All three are independent — pick the ones that match your setup. A fourth storage-adjacent optimization, <link>Log2RAM</link>, reduces SSD/NVMe wear by moving <code>/var/log</code> to a ramdisk — it lives on the Optional page because the ProxMenux Customizable menu groups it there."
|
||||
},
|
||||
"notTrackedTitle": "None of these are in the Uninstall menu",
|
||||
"notTrackedBody": "Unlike most post-install optimizations, the three Storage options are <strong>not currently tracked</strong> in the Uninstall Optimizations flow. If you apply them and later want to revert, you'll have to do it by hand. The manual rollback commands are shown below each section.",
|
||||
@@ -148,6 +148,11 @@
|
||||
"href": "/docs/post-install/uninstall",
|
||||
"tail": " — revert ARC / vzdump changes."
|
||||
},
|
||||
{
|
||||
"label": "Log2RAM",
|
||||
"href": "/docs/post-install/optional#log2ram",
|
||||
"tail": " — reduces SSD/NVMe wear by ramdisk-backing /var/log; documented under Optional."
|
||||
},
|
||||
{
|
||||
"label": "Customizable Post-Install",
|
||||
"href": "/docs/post-install/customizable",
|
||||
|
||||
Reference in New Issue
Block a user