mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-03 03:12:07 +00:00
Bring the web documentation in line with develop in every language
- English pages describe backups, the manifest, ROCm GPUs, OCI host directories and storage as develop does, and say user instead of operator - New paragraph on host directories mounted with everything the host has mounted inside them, and a new update rule for host directories - The Proxmox Backup Server section of the post-install page is translated into every language - Spanish and the other six languages follow the updated English
This commit is contained in:
@@ -19,8 +19,16 @@
|
||||
"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." }
|
||||
{
|
||||
"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": {
|
||||
@@ -34,12 +42,42 @@
|
||||
"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 user 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>." }
|
||||
{
|
||||
"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 user 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": {
|
||||
@@ -57,11 +95,31 @@
|
||||
"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>. The pmxcfs database at <code>/var/lib/pve-cluster/config.db</code> is captured separately via <code>sqlite3 .backup</code> to produce a consistent snapshot without stopping the cluster, and the <code>.db-wal</code> / <code>.db-shm</code> sidecars are excluded from the rsync; when <code>sqlite3</code> is not available a <code>config.db.raw-fallback</code> is dropped instead, and the restore promotes it on import. Volatile subpaths (bash history, caches, trash) are excluded 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 user-facing <em>Host backup started</em> event is emitted. Silent if no channels are configured." }
|
||||
{
|
||||
"step": "1",
|
||||
"name": "rootfs assembly",
|
||||
"detail": "Runs <code>rsync -a</code> for each selected path into <code>staging_root/rootfs/</code>. The pmxcfs database at <code>/var/lib/pve-cluster/config.db</code> is captured separately via <code>sqlite3 .backup</code> to produce a consistent snapshot without stopping the cluster, and the <code>.db-wal</code> / <code>.db-shm</code> sidecars are excluded from the rsync; when <code>sqlite3</code> is not available a <code>config.db.raw-fallback</code> is dropped instead, and the restore promotes it on import. Volatile subpaths (bash history, caches, trash) are excluded 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, a user-facing <em>Host backup started</em> event is emitted. Silent if no channels are configured."
|
||||
}
|
||||
]
|
||||
},
|
||||
"included": {
|
||||
@@ -117,9 +175,18 @@
|
||||
"heading": "Writing to the destination",
|
||||
"intro": "Once the user 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." }
|
||||
{
|
||||
"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": {
|
||||
@@ -133,9 +200,21 @@
|
||||
"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." }
|
||||
{
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
{
|
||||
"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."
|
||||
"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 user error but not against disk failure."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
{
|
||||
"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."
|
||||
"detail": "The path the user 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",
|
||||
@@ -117,7 +117,7 @@
|
||||
{
|
||||
"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."
|
||||
"detail": "ProxMenux generates the keypair as above but displays the full <code>authorized_keys</code> line for the user to paste manually into the server. The admin password never leaves the ProxMenux host because it is never asked for."
|
||||
},
|
||||
{
|
||||
"mode": "generate-pct",
|
||||
@@ -127,7 +127,7 @@
|
||||
{
|
||||
"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."
|
||||
"detail": "ProxMenux scans <code>/root/.ssh/</code> and <code>$HOME/.ssh/</code> for parseable ed25519/RSA private keys and lists them. The user picks one or browses manually to a non-standard path."
|
||||
},
|
||||
{
|
||||
"mode": "none",
|
||||
@@ -150,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"file": "borg-pass-NAME.txt",
|
||||
"content": "Passphrase for the target NAMEd above (<code>chmod 600</code>). Only present when the operator chose repokey encryption."
|
||||
"content": "Passphrase for the target NAMEd above (<code>chmod 600</code>). Only present when the user chose repokey encryption."
|
||||
}
|
||||
],
|
||||
"outro": "Saving the target is optional. Declining the save prompt omits the named target configuration, but does not remove a generated SSH key. If repokey encryption is enabled without a saved target, ProxMenux still saves the passphrase in the legacy borg-pass.txt file."
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
"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."
|
||||
"body": "Wherever the backup is stored, it always holds the same three blocks described in <em>How it works</em>: the filesystem (<code>rootfs/</code>), the metadata (<code>metadata/</code>) and the manifest (<code>manifest.json</code>). Extracting a local <code>.tar.zst</code> archive, restoring a <code>.pxar</code> from PBS or extracting a Borg archive all yield an identical directory tree. The restore therefore always reads the same files, whichever destination the backup comes from."
|
||||
},
|
||||
"extractStandalone": {
|
||||
"heading": "Extracting a backup outside of ProxMenux",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"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>:",
|
||||
"intro": "The local destination is a <strong>single persisted target directory</strong> — not a list. ProxMenux stores the user'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.",
|
||||
@@ -30,11 +30,23 @@
|
||||
"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." }
|
||||
{
|
||||
"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 user 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."
|
||||
"notMountedFallback": "When no USB device is detected, the submenu falls back to a plain inputbox. The user 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",
|
||||
@@ -45,7 +57,7 @@
|
||||
"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.",
|
||||
"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 user 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."
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"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.",
|
||||
"intro": "ProxMenux discovers PBS repositories from two sources on every backup. The user picks one from a unified menu; for that run, the choice determines which server and datastore the backup is sent to, the password it authenticates with and the <em>fingerprint</em> that validates the certificate.",
|
||||
"sourceRows": [
|
||||
{
|
||||
"source": "Proxmox storage.cfg (auto-discovered)",
|
||||
@@ -37,43 +37,43 @@
|
||||
}
|
||||
],
|
||||
"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."
|
||||
"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 user 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 uses <code>env</code> to pass the password, encryption password and fingerprint to the client as environment variables.",
|
||||
"cmd": "env \\\n PBS_PASSWORD=\"$HB_PBS_SECRET\" \\\n PBS_ENCRYPTION_PASSWORD=\"$HB_PBS_ENC_PASS\" \\\n PBS_FINGERPRINT=\"$HB_PBS_FINGERPRINT\" \\\n proxmox-backup-client backup \\\n hostcfg.pxar:$staging_root \\\n --repository USER@REALM@HOST:DATASTORE \\\n --backup-type host \\\n --backup-id hostcfg-HOSTNAME \\\n --backup-time BACKUP-EPOCH \\\n [--keyfile /usr/local/share/proxmenux/pbs-key.conf]",
|
||||
"backupIdTitle": "Backup ID naming",
|
||||
"backupIdBody": "The default backup ID is <code>hostcfg-HOSTNAME</code>. The operator is asked to confirm or edit it before the upload; any characters outside <code>[A-Za-z0-9_-]</code> are stripped and trailing dashes are trimmed. Reusing the same ID across runs is intentional — PBS treats the ID as a <em>group</em>, and every subsequent backup appears as a new backup inside that group, sharing dedup with prior runs.",
|
||||
"backupIdBody": "The default backup ID is <code>hostcfg-HOSTNAME</code>. The user 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."
|
||||
"pxarBody": "The <code>.pxar</code> archive packs the whole working directory: the host filesystem (<code>rootfs/</code>), the metadata (<code>metadata/</code>) and the manifest (<code>manifest.json</code>). On restore, ProxMenux compares the manifest with the target host to tell whether they are equivalent, whether the hardware changed or whether it is a different machine, and adjusts the process accordingly. Older backups, made by earlier ProxMenux versions that packed only the filesystem, still restore correctly: the restore detects that legacy format and rearranges it automatically."
|
||||
},
|
||||
"encryption": {
|
||||
"heading": "Client-side encryption",
|
||||
"glossaryHint": "This section uses the terms <em>keyfile</em>, <em>passphrase</em> and <em>recovery envelope</em>. The <glosarioLink>glossary</glosarioLink> summarises the differences in one sentence per term.",
|
||||
"intro": "PBS client-side keyfile encryption encrypts chunks on the source host before upload. In ProxMenux encryption is optional; when enabled, whether to also upload a passphrase-wrapped copy of the keyfile to PBS as a recovery escrow is an explicit yes/no choice the operator makes at setup time (and can revisit later from the Monitor). The keyfile itself always lives at one canonical path on the host — <code>/usr/local/share/proxmenux/pbs-key.conf</code> — and is reused silently by every subsequent encrypted backup.",
|
||||
"intro": "PBS client-side keyfile encryption encrypts chunks on the source host before upload. In ProxMenux encryption is optional; when enabled, whether to also upload a passphrase-wrapped copy of the keyfile to PBS as a recovery escrow is an explicit yes/no choice the user makes at setup time (and can revisit later from the Monitor). The keyfile itself always lives at one canonical path on the host — <code>/usr/local/share/proxmenux/pbs-key.conf</code> — and is reused silently by every subsequent encrypted backup.",
|
||||
"keyfileTitle": "Setting up the keyfile",
|
||||
"keyfileBody": "The encryption prompt runs at the first encrypted backup on the host. Step one asks whether to encrypt the backup: <em>No</em> continues without encryption; <em>Yes</em> moves to step two. Step two depends on whether a keyfile is already installed at <code>/usr/local/share/proxmenux/pbs-key.conf</code>. If it is, the installed keyfile is reused silently and the backup proceeds — subsequent backups never re-ask. If it is not, a two-option menu asks how to set one up. <em>Generate a new keyfile</em> runs <code>proxmox-backup-client key create --kdf none</code>. <em>Use an existing keyfile</em> auto-detects a PVE-managed keyfile: when the selected PBS repository is registered in Proxmox with an encryption key at <code>/etc/pve/priv/storage/<NAME>.enc</code>, that file is copied to the ProxMenux canonical path silently and the flow continues. When no PVE-managed keyfile matches the repository, an input prompts for the absolute path where the operator has already placed the keyfile on this host. Either way, the file lands at the canonical path with <code>chmod 600</code>.",
|
||||
"keyfileBody": "The encryption prompt runs at the first encrypted backup on the host. Step one asks whether to encrypt the backup: <em>No</em> continues without encryption; <em>Yes</em> moves to step two. Step two depends on whether a keyfile is already installed at <code>/usr/local/share/proxmenux/pbs-key.conf</code>. If it is, the installed keyfile is reused silently and the backup proceeds — subsequent backups never re-ask. If it is not, a two-option menu asks how to set one up. <em>Generate a new keyfile</em> runs <code>proxmox-backup-client key create --kdf none</code>. <em>Use an existing keyfile</em> auto-detects a PVE-managed keyfile: when the selected PBS repository is registered in Proxmox with an encryption key at <code>/etc/pve/priv/storage/<NAME>.enc</code>, that file is copied to the ProxMenux canonical path silently and the flow continues. When no PVE-managed keyfile matches the repository, an input prompts for the absolute path where the user has already placed the keyfile on this host. Either way, the file lands at the canonical path with <code>chmod 600</code>.",
|
||||
"modesTitle": "Per-host or shared keyfile",
|
||||
"modesIntro": "Both operating models are supported and neither is enforced — the choice belongs to the operator based on how their fleet is organised.",
|
||||
"modesIntro": "Both operating models are supported and neither is enforced — the choice belongs to the user based on how their fleet is organised.",
|
||||
"modesPerHostTitle": "Per-host keyfile (default)",
|
||||
"modesPerHostBody": "Each host generates its own keyfile the first time it enables PBS encryption. Isolation is maximum: compromising the keyfile of one host does not expose the backups of any other. Recommended for production fleets and for environments where hosts have different owners or compliance boundaries.",
|
||||
"modesSharedTitle": "Shared keyfile (import on every host)",
|
||||
"modesSharedBody": "One master keyfile generated once and installed on every host via the <em>Use an existing keyfile</em> option. Management is simpler: a single secret to safeguard, and any host can decrypt the archives of any other (useful for consolidation, cross-host restore drills or centralised backup verification). The trade-off is that a leak of the shared keyfile exposes every host at once. Recommended for homelabs and for fleets where all hosts have the same owner and trust boundary.",
|
||||
"recoveryTitle": "Upload key to PBS — the operator decides",
|
||||
"recoveryTitle": "Upload key to PBS — the user decides",
|
||||
"recoveryBody": "Right after the keyfile is chosen, ProxMenux asks a single explicit question — <em>Upload key to PBS?</em> — with two answers. <strong>No recovery artifact is uploaded to PBS until the operator answers Yes.</strong> The default is <em>No, keep local only</em>: the keyfile stays at the canonical local path, ProxMenux uploads no recovery artifact to PBS, and the operator handles the offsite copy themselves (the Monitor’s <em>Download keyfile</em> button in the PBS destination row provides one on demand). Choosing <em>Yes, upload</em> prompts for a recovery passphrase — twice, with match validation — and ProxMenux wraps the keyfile with that passphrase using AES-256-CBC and PBKDF2 (600 000 iterations, random salt) to produce <code>pbs-key.recovery.enc</code>. Every subsequent encrypted backup uploads that envelope to PBS as a paired backup group. Neither the passphrase nor a plaintext copy of the keyfile is sent to PBS during these uploads. The choice can be changed later from the Monitor at any time — the escrow toggle inline in the PBS destination row flips Yes/No and applies immediately (Start uploading, Stop uploading, or Update passphrase, matching the current state).",
|
||||
"blobUploadTitle": "Paired backup group on PBS",
|
||||
"blobUploadBody1": "When the escrow mode is <em>Yes, upload</em>, ProxMenux uploads the recovery envelope to PBS after every encrypted backup as a second, paired backup group with the same name as the host group but ending in <code>-keyrecovery</code>. The two groups sit next to each other in the datastore listing — one for host backups, one for recovery envelopes. <strong>The envelope never leaves the host in plaintext</strong>: encryption happens on the host itself with AES-256-CBC and PBKDF2 before any bytes go on the wire (<code>openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt</code>). PBS receives only the already-encrypted envelope.",
|
||||
"blobUploadBody1": "When the answer is <em>Yes, upload</em>, ProxMenux uploads the recovery envelope to PBS after every encrypted backup as a second, paired backup group with the same name as the host group but ending in <code>-keyrecovery</code>. The two groups sit next to each other in the datastore listing — one for host backups, one for recovery envelopes. <strong>The envelope never leaves the host in plaintext</strong>: encryption happens on the host itself with AES-256-CBC and PBKDF2 before any bytes go on the wire (<code>openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt</code>). PBS receives only the already-encrypted envelope.",
|
||||
"envelopeSecurityTitle": "Is uploading the keyrecovery file to PBS a security risk?",
|
||||
"envelopeSecurityBody": "The keyrecovery file is encrypted in transit and at rest. The recovery passphrase never leaves the host: encryption happens locally before the upload, and PBS only ever sees the already-encrypted result. A PBS administrator — or anyone with access to the datastore — can download the keyrecovery file, but without the recovery passphrase they cannot read the keyfile inside: it is opaque ciphertext. Reconstructing the keyfile requires both pieces at once, the keyrecovery file and the passphrase, and only the operator holds both.",
|
||||
"envelopeSecurityBody": "The keyrecovery file is encrypted in transit and at rest. The recovery passphrase never leaves the host: encryption happens locally before the upload, and PBS only ever sees the already-encrypted result. A PBS administrator — or anyone with access to the datastore — can download the keyrecovery file, but without the recovery passphrase they cannot read the keyfile inside: it is opaque ciphertext. Reconstructing the keyfile requires both pieces at once, the keyrecovery file and the passphrase, and only the user holds both.",
|
||||
"blobUploadConstraintTitle": "Why two groups instead of a single encrypted one",
|
||||
"blobUploadConstraintBody": "A single <code>proxmox-backup-client backup</code> invocation encrypts every one of its archives with the same keyfile, or none at all — there is no in-between. The host backup has to be encrypted with the host keyfile, but the recovery envelope cannot ride along in that same upload, because it would end up encrypted with the very keyfile it contains: on a freshly reinstalled host there would be no way to open it (you would need the keyfile to decrypt the keyfile). That is why the two artefacts are uploaded independently and appear as two separate groups: the host backup is encrypted by the PBS client with the keyfile, and the recovery envelope is encrypted by ProxMenux with the passphrase before upload. Two different encryption layers protecting two different assets.",
|
||||
"blobUploadImageAlt": "PBS UI showing the hostcfg-HOSTNAME and hostcfg-HOSTNAME-keyrecovery backup groups adjacent in the datastore listing.",
|
||||
"blobUploadImageCaption": "PBS UI — the paired backup groups. The main group holds the host backups; the -keyrecovery group holds the passphrase-wrapped keyfile.",
|
||||
"recoverTitle": "Recovery on a fresh host",
|
||||
"recoverBody": "When the local keyfile is missing at the canonical path — on a freshly reinstalled host, or after an explicit removal — the restore flow tries three sources in order and stops at the first one that produces a usable keyfile. First, when the selected PBS repository has a matching PVE-managed keyfile at <code>/etc/pve/priv/storage/<NAME>.enc</code>, that file is copied to the canonical path silently. Second, when a <code>-keyrecovery</code> group is available on PBS, the newest snapshot is downloaded and the operator is asked for the recovery passphrase to decrypt it. Third, when neither of the above works or the operator declines them, an input prompts for the absolute path where the keyfile lives on this host and copies it to the canonical path. The Monitor exposes the same import flow inline in the backup detail modal — an amber panel appears at the top of the modal when Restore, Download or View contents would need a keyfile that is not installed. When a keyfile is installed but does not match the one that encrypted the backup, PBS returns a <code>wrong key — manifest's key XX does not match provided key YY</code> error; the same three actions display a structured amber panel with the required manifest fingerprint prominently rendered, so the operator can identify which keyfile to import to open that specific backup.",
|
||||
"recoverBody": "When the local keyfile is missing at the canonical path — on a freshly reinstalled host, or after an explicit removal — the restore flow tries three sources in order and stops at the first one that produces a usable keyfile. First, when the selected PBS repository has a matching PVE-managed keyfile at <code>/etc/pve/priv/storage/<NAME>.enc</code>, that file is copied to the canonical path silently. Second, when a <code>-keyrecovery</code> group is available on PBS, the newest snapshot is downloaded and the user is asked for the recovery passphrase to decrypt it. Third, when neither of the above works or the user declines them, an input prompts for the absolute path where the keyfile lives on this host and copies it to the canonical path. The Monitor exposes the same import flow inline in the backup detail modal — an amber panel appears at the top of the modal when Restore, Download or View contents would need a keyfile that is not installed. When a keyfile is installed but does not match the one that encrypted the backup, PBS returns a <code>wrong key — manifest's key XX does not match provided key YY</code> error; the same three actions display a structured amber panel with the required manifest fingerprint prominently rendered, so the user can identify which keyfile to import to open that specific backup.",
|
||||
"monitorManagementTitle": "Managing the keyfile from the Monitor",
|
||||
"monitorManagementBody": "The Monitor exposes the keyfile controls inline in each PBS destination row of the backup configuration page. The row shows three actions: <em>Download</em> exports the current keyfile to the operator's browser, <em>Upload</em> replaces the installed keyfile with one supplied by the operator (the previous file is overwritten only after the new one lands successfully at the canonical path), and <em>Delete</em> removes the keyfile from the host. The recovery-escrow toggle is a single inline control next to those actions: a Yes/No radio for <em>Upload key to PBS?</em>, a passphrase field when Yes is selected, and a contextual Apply button that reads <em>Start uploading</em>, <em>Stop uploading</em> or <em>Update passphrase</em> depending on the current state. Every action goes through the same Flask endpoints that the shell TUI uses, so the on-disk effect and the state on PBS are identical whichever surface the operator picks."
|
||||
"monitorManagementBody": "The Monitor shows the keyfile controls in each PBS destination row of the backup configuration page. The row has three actions: <em>Download</em> exports the current keyfile to the user's browser, <em>Upload</em> replaces the installed keyfile with one supplied by the user (the previous file is overwritten only after the new one has arrived correctly at its path), and <em>Delete</em> removes the keyfile from the host. Right next to them is the switch of the recovery envelope: a Yes/No for <em>Upload key to PBS?</em>, a passphrase field that appears when Yes is selected, and a button that follows the current state: <em>Start uploading</em>, <em>Stop uploading</em> or <em>Update passphrase</em>. The Monitor controls and the shell dialog do the same underneath, so the result on disk and on PBS is identical whichever one is used."
|
||||
},
|
||||
"restoreAccess": {
|
||||
"heading": "Retrieval on the restore side",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
{
|
||||
"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>)."
|
||||
"content": "User-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",
|
||||
@@ -121,7 +121,7 @@
|
||||
}
|
||||
],
|
||||
"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."
|
||||
"schemaBody": "ProxMenux ships a template that describes which fields the manifest must contain and the shape of each one. When a backup is generated, the manifest can be checked against that template automatically, so a collector that produced malformed JSON or a misspelled field is caught at once. The check is meant for the development of ProxMenux itself: on a system that does not have it installed, the backup works as usual and the manifest is generated all the same."
|
||||
},
|
||||
"applications": {
|
||||
"heading": "The application inventory",
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
},
|
||||
"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 user does not need to check the log to know whether a job succeeded."
|
||||
"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 — a user does not need to check the log to know whether a job succeeded."
|
||||
},
|
||||
"whereNext": {
|
||||
"heading": "Where to go next",
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
"<code>/api/system-info</code> is a public lightweight system-information endpoint, including <code>health.status</code>. It is not decorated with the token requirement. /api/auth/status is another public endpoint suitable for a reachability check."
|
||||
],
|
||||
"cryptoTitle": "Cryptography and storage",
|
||||
"cryptoIntro": "ProxMenux Monitor is open source — none of this is secret. Documenting the stack here explicitly is a deliberate choice: operators who store credentials on their host deserve to know how those credentials are protected before they decide to trust them. The algorithms below are the same ones the code in <code>scripts/auth_manager.py</code> uses; this section is a contract, not a marketing promise.",
|
||||
"cryptoIntro": "ProxMenux Monitor is open source — none of this is secret. Documenting the stack here explicitly is a deliberate choice: users who store credentials on their host deserve to know how those credentials are protected before they decide to trust them. The algorithms below are the same ones the code in <code>scripts/auth_manager.py</code> uses; this section is a contract, not a marketing promise.",
|
||||
"headerAsset": "Asset",
|
||||
"headerAlgo": "Algorithm",
|
||||
"headerWhere": "Where it lives",
|
||||
@@ -158,9 +158,9 @@
|
||||
"authJsonTitle": "auth.json — what it contains and how it's protected",
|
||||
"authJsonBody": "Everything ProxMenux Monitor needs to authenticate you lives in a single file: <code>/root/.config/proxmenux-monitor/auth.json</code>, mode <code>0600</code>, owner <code>root</code>. The file holds <em>hashes</em> (PBKDF2) and <em>signing material</em> (<code>jwt_secret</code>, <code>totp_secret</code>) — never a plaintext password. Treat it like any other root-only secret: if you back up or replicate the host, encrypt the destination, and never commit it to version control.",
|
||||
"rotateTitle": "Rotating jwt_secret invalidates all existing JWTs",
|
||||
"rotateBody": "If <code>auth.json</code> is regenerated (manual delete, reinstall, restore from a backup with a different secret) the <code>jwt_secret</code> changes and every previously-issued JWT — both interactive sessions and long-lived API tokens — fails verification with \"Invalid or expired token\". The UI flags affected API tokens with an <strong>Invalid — regenerate</strong> badge so the operator knows to revoke and re-mint them; Home Assistant / scripts / any external client needs a fresh token after that.",
|
||||
"rotateBody": "If <code>auth.json</code> is regenerated (manual delete, reinstall, restore from a backup with a different secret) the <code>jwt_secret</code> changes and every previously-issued JWT — both interactive sessions and long-lived API tokens — fails verification with \"Invalid or expired token\". The UI flags affected API tokens with an <strong>Invalid — regenerate</strong> badge so the user knows to revoke and re-mint them; Home Assistant / scripts / any external client needs a fresh token after that.",
|
||||
"recoverTitle": "Recovering a lost password",
|
||||
"recoverIntro": "There is no online \"forgot password\" flow — by design, since the dashboard runs on the operator's own host and the recovery path is shell access to that host. ProxMenux ships a guided reset inside the configuration menu so you don't have to hand-edit <code>auth.json</code>:",
|
||||
"recoverIntro": "There is no online \"forgot password\" flow — by design, since the dashboard runs on the user's own host and the recovery path is shell access to that host. ProxMenux ships a guided reset inside the configuration menu so you don't have to hand-edit <code>auth.json</code>:",
|
||||
"survivesTitle": "What survives the reset",
|
||||
"survivesBody": "Only the interactive login is wiped. The <code>jwt_secret</code> and the registered <code>api_tokens</code> are preserved — so Home Assistant and any other script using a long-lived API token continue to work without reconfiguration. If you want a fully clean slate (also rotate the JWT secret), delete <code>auth.json</code> manually and restart the service. The next launch generates a fresh secret and all old tokens become invalid.",
|
||||
"physicalTitle": "Physical-access prerequisite",
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
"shareTitle": "Sharing prompts with the community",
|
||||
"shareIntro": "The <em>Export</em> button writes your current custom prompt to a file (<code>.txt</code> / <code>.md</code>) you can keep as a backup or hand to someone else. <em>Import</em> pulls one back in. The third button next to them links to a public community gallery on GitHub:",
|
||||
"shareLinkLabel": "github.com/MacRimi/ProxMenux/discussions — Share custom prompts for AI notifications",
|
||||
"shareOutro": "Browse the discussion to see what other operators have built — terse pager-style alerts, verbose technical reports, language-specific variants. If you tweak yours and like the result, post it there: even a one-paragraph description of what your prompt optimises for helps people pick a good starting point. Feedback on what works and what doesn't is equally welcome."
|
||||
"shareOutro": "Browse the discussion to see what other users have built — terse pager-style alerts, verbose technical reports, language-specific variants. If you tweak yours and like the result, post it there: even a one-paragraph description of what your prompt optimises for helps people pick a good starting point. Feedback on what works and what doesn't is equally welcome."
|
||||
},
|
||||
"suggestions": {
|
||||
"heading": "AI Suggestions (BETA)",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"If a translated key is missing, only that text falls back to English instead of rendering a broken placeholder."
|
||||
],
|
||||
"scopeTitle": "Browser-local preference",
|
||||
"scopeBody": "The selection is stored in <code>localStorage</code> under <code>proxmenux-ui-language</code>. It is therefore specific to this browser profile and device, not a node-wide setting. Each operator can choose a different Monitor language."
|
||||
"scopeBody": "The selection is stored in <code>localStorage</code> under <code>proxmenux-ui-language</code>. It is therefore specific to this browser profile and device, not a node-wide setting. Each user can choose a different Monitor language."
|
||||
},
|
||||
"navigationOrder": {
|
||||
"heading": "Navigation order",
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
],
|
||||
"obsDedupTitle": "How dedup and re-notification work",
|
||||
"obsDedupBody1": "Observations are deduplicated by their <strong>signature</strong> — a stable fingerprint of the error type, device and key fields of the kernel line. The same event repeating bumps the <code>occurrence_count</code> on the existing row rather than creating a new one. A <strong>different signature</strong> on the same disk creates a new observation and is treated as a new event for notification purposes.",
|
||||
"obsDedupBody2": "Notifications follow an anti-cascade rule: the first occurrence of a given (disk, signature, severity) combination pages the operator, and ProxMenux then waits 24 hours before pinging again about the same combination — even if the count keeps climbing. Escalating severity (WARNING → CRITICAL) breaks the cooldown so the operator is told when things get worse, not just when they happen.",
|
||||
"obsDedupBody2": "Notifications follow an anti-cascade rule: the first occurrence of a given (disk, signature, severity) combination pages the user, and ProxMenux then waits 24 hours before pinging again about the same combination — even if the count keeps climbing. Escalating severity (WARNING → CRITICAL) breaks the cooldown so the user is told when things get worse, not just when they happen.",
|
||||
"obsDismissTitle": "Permanent history vs active errors",
|
||||
"obsDismissBody1": "An observation cannot be dismissed, hidden or removed. Each different observation of a disk is one card; the same one seen again adds to its occurrence count and moves its last date instead of creating a new card. The history belongs to the disk, identified by its serial number: a disk plugged where another one was starts with none, and a disk the kernel renames keeps its own.",
|
||||
"obsDismissBody2": "Resolving or dismissing on the active-error side (Health Monitor) is independent of the observations — the observation persists past the active error's auto-resolve. That's the whole point: it survives, so a transient warning from last week is still visible on the disk card today. See <link>Health Monitor</link> for the active-error side of the same picture."
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"updatesLinkLabel": "dedicated Updates page",
|
||||
"updatesLinkTail": "for integrated and custom updaters, Docker recreation, bulk selection, safety options and scheduling.",
|
||||
"mountsTitle": "Tab 4 — Mounts (LXC only, when present)",
|
||||
"mountsImageAlt": "LXC drill-in modal — Mounts tab listing every mount point the container is using: PVE volumes, host binds, binds from PVE storage and ad-hoc NFS/CIFS mounts the operator mounted from inside the CT. Each card carries a type badge, capacity bar, used/total bytes, mount options, and a colour-coded state dot (green healthy, amber readonly/divergent, red stale)",
|
||||
"mountsImageAlt": "LXC drill-in modal — Mounts tab listing every mount point the container is using: PVE volumes, host binds, binds from PVE storage and ad-hoc NFS/CIFS mounts the user mounted from inside the CT. Each card carries a type badge, capacity bar, used/total bytes, mount options, and a colour-coded state dot (green healthy, amber readonly/divergent, red stale)",
|
||||
"mountsImageCaption": "Mounts tab — only renders for LXC containers, and only when at least one mount point or ad-hoc remote mount is present. A CT without mounts gets no tab.",
|
||||
"mountsIntro": "Proxmox's own UI shows the mount-point entries defined in the container config (<code>mpX</code>) but stops there — anything you mount from inside the CT later (<code>mount.cifs</code>, NFS via <code>autofs</code>, …) is invisible. This tab merges <strong>both views</strong>: the configured mounts <strong>and</strong> the runtime mounts ProxMenux probes from inside the container, with a per-mount health status and a capacity bar wherever the backend can resolve one.",
|
||||
"mountTypesTitle": "Types of mount detected",
|
||||
|
||||
@@ -19,9 +19,18 @@
|
||||
{
|
||||
"flow": {
|
||||
"nodes": [
|
||||
{ "label": "Host inventory", "detail": "/dev/dri/renderD128\nGID 993 · Intel" },
|
||||
{ "label": "ProxMenux", "detail": "vendor and\npermissions checked" },
|
||||
{ "label": "LXC", "detail": "same device\neffective GID" }
|
||||
{
|
||||
"label": "Host inventory",
|
||||
"detail": "/dev/dri/renderD128\nGID 993 · Intel"
|
||||
},
|
||||
{
|
||||
"label": "ProxMenux",
|
||||
"detail": "vendor and\npermissions checked"
|
||||
},
|
||||
{
|
||||
"label": "LXC",
|
||||
"detail": "same device\neffective GID"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -33,12 +42,32 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Source", "What is read", "What the installer does"],
|
||||
"headers": [
|
||||
"Source",
|
||||
"What is read",
|
||||
"What the installer does"
|
||||
],
|
||||
"rows": [
|
||||
["Docker Compose", "<code>devices</code>, <code>group_add</code>, <code>deploy.resources</code> and NVIDIA requests", "Each requirement becomes a device request shown for review"],
|
||||
["Catalog profile", "The GPU, Coral, OpenCL, USB or FUSE support the application actually has", "Only the options validated for that image are offered"],
|
||||
["Image metadata and documentation", "VA-API, Selkies, LinuxServer mods or the NVIDIA runtime", "The documented variables and preparation are added"],
|
||||
["User selection", "CPU only, Intel/AMD, OpenCL, NVIDIA or an optional device", "The selection is stored in the instance contract"]
|
||||
[
|
||||
"Docker Compose",
|
||||
"<code>devices</code>, <code>group_add</code>, <code>deploy.resources</code> and NVIDIA requests",
|
||||
"Each requirement becomes a device request shown for review"
|
||||
],
|
||||
[
|
||||
"Catalog profile",
|
||||
"The GPU, Coral, OpenCL, USB or FUSE support the application actually has",
|
||||
"Only the options validated for that image are offered"
|
||||
],
|
||||
[
|
||||
"Image metadata and documentation",
|
||||
"VA-API, Selkies, LinuxServer mods or the NVIDIA runtime",
|
||||
"The documented variables and preparation are added"
|
||||
],
|
||||
[
|
||||
"User selection",
|
||||
"CPU only, Intel/AMD, OpenCL, NVIDIA or an optional device",
|
||||
"The selection is stored in the instance contract"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -57,16 +86,52 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Type", "Identity", "Validation"],
|
||||
"headers": [
|
||||
"Type",
|
||||
"Identity",
|
||||
"Validation"
|
||||
],
|
||||
"rows": [
|
||||
["Intel/AMD DRM", "<code>/dev/dri/renderD*</code> and <code>/sys/class/drm/NODE/device/vendor</code>", "A character device with vendor <code>0x8086</code> (Intel) or <code>0x1002</code> (AMD)"],
|
||||
["AMD OpenCL", "The render node, plus <code>/dev/kfd</code> when the profile needs it", "Existence, type, vendor, permissions and declared compatibility"],
|
||||
["AMD ROCm", "The render node, <code>/dev/kfd</code> and the generation the compute driver reports in <code>/sys/class/kfd/kfd/topology/nodes/*/properties</code>", "A generation the ROCm image of the application carries kernels for, the RDNA2 GPUs it names and newer ones, is offered as it is. The other GPUs of those families, such as the Radeon 680M and 780M, are offered as experimental and are never the proposed option: ROCm does not support them officially, the application presents them as the generation of their family (<code>HSA_OVERRIDE_GFX_VERSION</code>) and the installer asks for confirmation before the larger image is downloaded. An older GPU, such as the integrated graphics of a Ryzen 5000U, is not offered and Immich keeps recognition on the CPU"],
|
||||
["NVIDIA", "<code>nvidia-smi</code> and <code>nvidia-container-cli</code>", "GPU, UUID, PCI bus, driver version, Toolkit, <code>/dev/nvidia*</code> nodes, binaries and libraries"],
|
||||
["Coral PCIe/M.2", "<code>/dev/apex_N</code> and its link in <code>/sys/dev/char/MAJOR:MINOR</code>", "Character node, major/minor, owner, GID and permissions"],
|
||||
["USB and serial", "<code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code> or <code>/dev/bus/usb/BBB/DDD</code>", "Character node; for USB also vendor, product and serial when sysfs publishes them"],
|
||||
["KVM, TUN, FUSE, video and generic SCSI", "<code>/dev/kvm</code>, <code>/dev/net/tun</code>, <code>/dev/fuse</code>, <code>/dev/videoN</code> or <code>/dev/sgN</code>", "Supported path, node type and effective permissions"],
|
||||
["Optical drive", "<code>/dev/srN</code>", "A block device"]
|
||||
[
|
||||
"Intel/AMD DRM",
|
||||
"<code>/dev/dri/renderD*</code> and <code>/sys/class/drm/NODE/device/vendor</code>",
|
||||
"A character device with vendor <code>0x8086</code> (Intel) or <code>0x1002</code> (AMD)"
|
||||
],
|
||||
[
|
||||
"AMD OpenCL",
|
||||
"The render node, plus <code>/dev/kfd</code> when the profile needs it",
|
||||
"Existence, type, vendor, permissions and declared compatibility"
|
||||
],
|
||||
[
|
||||
"AMD ROCm",
|
||||
"The render node, <code>/dev/kfd</code> and the generation the compute driver reports in <code>/sys/class/kfd/kfd/topology/nodes/*/properties</code>",
|
||||
"A GPU of a generation the application's ROCm image supports (the RDNA2 GPUs it names, and newer ones) is offered as it is. Other GPUs of those families, such as the Radeon 680M and 780M, are offered as experimental and are never the proposed option. ROCm does not support them officially: the application presents them as the generation of their family (<code>HSA_OVERRIDE_GFX_VERSION</code>), and the installer asks for confirmation before downloading the larger image. An older GPU, such as the integrated graphics of a Ryzen 5000U, is not offered, and Immich keeps recognition on the CPU"
|
||||
],
|
||||
[
|
||||
"NVIDIA",
|
||||
"<code>nvidia-smi</code> and <code>nvidia-container-cli</code>",
|
||||
"GPU, UUID, PCI bus, driver version, Toolkit, <code>/dev/nvidia*</code> nodes, binaries and libraries"
|
||||
],
|
||||
[
|
||||
"Coral PCIe/M.2",
|
||||
"<code>/dev/apex_N</code> and its link in <code>/sys/dev/char/MAJOR:MINOR</code>",
|
||||
"Character node, major/minor, owner, GID and permissions"
|
||||
],
|
||||
[
|
||||
"USB and serial",
|
||||
"<code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code> or <code>/dev/bus/usb/BBB/DDD</code>",
|
||||
"Character node; for USB also vendor, product and serial when sysfs publishes them"
|
||||
],
|
||||
[
|
||||
"KVM, TUN, FUSE, video and generic SCSI",
|
||||
"<code>/dev/kvm</code>, <code>/dev/net/tun</code>, <code>/dev/fuse</code>, <code>/dev/videoN</code> or <code>/dev/sgN</code>",
|
||||
"Supported path, node type and effective permissions"
|
||||
],
|
||||
[
|
||||
"Optical drive",
|
||||
"<code>/dev/srN</code>",
|
||||
"A block device"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -79,12 +144,30 @@
|
||||
{
|
||||
"steps": {
|
||||
"items": [
|
||||
{ "title": "The template offers its profiles", "body": "For example CPU only, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL or NVIDIA. The options belong to the image, not to a common menu." },
|
||||
{ "title": "A profile is chosen", "body": "It defines the device nodes, environment, mods or runtime the application needs." },
|
||||
{ "title": "A path is proposed", "body": "For DRM, <code>/dev/dri/renderD128</code>, which can be changed on a host with several render nodes. For USB or serial, the concrete node is selected." },
|
||||
{ "title": "Validation", "body": "Existence, type, allowed vendor, permissions and GID are checked. A mismatch stops the operation." },
|
||||
{ "title": "The contract is written", "body": "Path, mode, GID, write access and profile are recorded for updates and recreations." },
|
||||
{ "title": "Attach and test", "body": "<code>pct set</code> adds the <code>devN</code> entry and access is then checked inside the LXC. LinuxServer images are also checked as user <code>abc</code>." }
|
||||
{
|
||||
"title": "The template offers its profiles",
|
||||
"body": "For example CPU only, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL or NVIDIA. The options belong to the image, not to a common menu."
|
||||
},
|
||||
{
|
||||
"title": "A profile is chosen",
|
||||
"body": "It defines the device nodes, environment, mods or runtime the application needs."
|
||||
},
|
||||
{
|
||||
"title": "A path is proposed",
|
||||
"body": "For DRM, <code>/dev/dri/renderD128</code>, which can be changed on a host with several render nodes. For USB or serial, the concrete node is selected."
|
||||
},
|
||||
{
|
||||
"title": "Validation",
|
||||
"body": "Existence, type, allowed vendor, permissions and GID are checked. A mismatch stops the operation."
|
||||
},
|
||||
{
|
||||
"title": "The contract is written",
|
||||
"body": "Path, mode, GID, write access and profile are recorded for updates and recreations."
|
||||
},
|
||||
{
|
||||
"title": "Attach and test",
|
||||
"body": "<code>pct set</code> adds the <code>devN</code> entry and access is then checked inside the LXC. LinuxServer images are also checked as user <code>abc</code>."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -98,10 +181,22 @@
|
||||
{
|
||||
"codeGrid": {
|
||||
"items": [
|
||||
{ "title": "Intel/AMD VA-API", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" },
|
||||
{ "title": "Coral PCIe/M.2", "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" },
|
||||
{ "title": "A specific USB device", "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" },
|
||||
{ "title": "AMD OpenCL", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" }
|
||||
{
|
||||
"title": "Intel/AMD VA-API",
|
||||
"code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0"
|
||||
},
|
||||
{
|
||||
"title": "Coral PCIe/M.2",
|
||||
"code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0"
|
||||
},
|
||||
{
|
||||
"title": "A specific USB device",
|
||||
"code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0"
|
||||
},
|
||||
{
|
||||
"title": "AMD OpenCL",
|
||||
"code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -116,13 +211,37 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Profile", "Translation", "Offered when"],
|
||||
"headers": [
|
||||
"Profile",
|
||||
"Translation",
|
||||
"Offered when"
|
||||
],
|
||||
"rows": [
|
||||
["Intel/AMD VA-API", "<code>/dev/dri</code> render node", "The application supports video acceleration"],
|
||||
["OpenCL", "Render node, <code>/dev/kfd</code> when needed and the official mod", "The image or profile documents it"],
|
||||
["NVIDIA", "Driver devices and libraries of the host", "The host has a working driver and the NVIDIA Container Toolkit"],
|
||||
["Coral", "<code>/dev/apex_0</code> or the USB bus", "The profile declares Coral support (Frigate)"],
|
||||
["USB, serial, FUSE", "A single device, a validated tree or an LXC feature", "The contract asks for it"]
|
||||
[
|
||||
"Intel/AMD VA-API",
|
||||
"<code>/dev/dri</code> render node",
|
||||
"The application supports video acceleration"
|
||||
],
|
||||
[
|
||||
"OpenCL",
|
||||
"Render node, <code>/dev/kfd</code> when needed and the official mod",
|
||||
"The image or profile documents it"
|
||||
],
|
||||
[
|
||||
"NVIDIA",
|
||||
"Driver devices and libraries of the host",
|
||||
"The host has a working driver and the NVIDIA Container Toolkit"
|
||||
],
|
||||
[
|
||||
"Coral",
|
||||
"<code>/dev/apex_0</code> or the USB bus",
|
||||
"The profile declares Coral support (Frigate)"
|
||||
],
|
||||
[
|
||||
"USB, serial, FUSE",
|
||||
"A single device, a validated tree or an LXC feature",
|
||||
"The contract asks for it"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -145,7 +264,9 @@
|
||||
"p": "These two commands on the host show whether the driver and the Toolkit are available:"
|
||||
},
|
||||
{
|
||||
"shell": { "code": "nvidia-smi -L\nnvidia-container-cli --version" }
|
||||
"shell": {
|
||||
"code": "nvidia-smi -L\nnvidia-container-cli --version"
|
||||
}
|
||||
},
|
||||
{
|
||||
"p": "On a host where the driver was installed by other means, the Toolkit is installed from the official stable repository:"
|
||||
@@ -159,7 +280,9 @@
|
||||
"p": "The inventory OCI manager Apps uses is the output of:"
|
||||
},
|
||||
{
|
||||
"shell": { "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" }
|
||||
"shell": {
|
||||
"code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"calloutInfo": {
|
||||
@@ -170,10 +293,26 @@
|
||||
{
|
||||
"cards": {
|
||||
"items": [
|
||||
{ "icon": "cpu", "title": "Inventory from the driver", "body": "<code>nvidia-container-cli</code> lists the device nodes, binaries, firmware and libraries of the installed driver." },
|
||||
{ "icon": "refresh", "title": "No fixed version", "body": "The template does not name library files. The profile is generated from the current host." },
|
||||
{ "icon": "shield", "title": "Read-only mounts", "body": "The host libraries are mounted read-only instead of being copied into the container." },
|
||||
{ "icon": "hardDrive", "title": "Driver changes", "body": "After a driver change the inventory is generated again before the affected LXCs start." }
|
||||
{
|
||||
"icon": "cpu",
|
||||
"title": "Inventory from the driver",
|
||||
"body": "<code>nvidia-container-cli</code> lists the device nodes, binaries, firmware and libraries of the installed driver."
|
||||
},
|
||||
{
|
||||
"icon": "refresh",
|
||||
"title": "No fixed version",
|
||||
"body": "The template does not name library files. The profile is generated from the current host."
|
||||
},
|
||||
{
|
||||
"icon": "shield",
|
||||
"title": "Read-only mounts",
|
||||
"body": "The host libraries are mounted read-only instead of being copied into the container."
|
||||
},
|
||||
{
|
||||
"icon": "hardDrive",
|
||||
"title": "Driver changes",
|
||||
"body": "After a driver change the inventory is generated again before the affected LXCs start."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -209,12 +348,32 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Request", "Translation", "Scope"],
|
||||
"headers": [
|
||||
"Request",
|
||||
"Translation",
|
||||
"Scope"
|
||||
],
|
||||
"rows": [
|
||||
["<code>/dev/dvb</code>, <code>/dev/snd</code> or <code>/dev/bus/usb</code>", "Each character node of the tree gets its own <code>devN</code> entry with the host mode and GID", "Only the requested tree, not the rest of <code>/dev</code>"],
|
||||
["<code>/dev/fuse</code>", "The node and, when the profile needs it, the <code>fuse=1</code> feature", "FUSE alone does not publish mounts to other LXCs"],
|
||||
["<code>/dev/net/tun</code>", "A <code>devN</code> entry at the same path inside the LXC", "The VPN or network configuration stays in the application"],
|
||||
["<code>/dev/kvm</code>", "A validated <code>devN</code> entry", "Offered only when the contract asks for it"]
|
||||
[
|
||||
"<code>/dev/dvb</code>, <code>/dev/snd</code> or <code>/dev/bus/usb</code>",
|
||||
"Each character node of the tree gets its own <code>devN</code> entry with the host mode and GID",
|
||||
"Only the requested tree, not the rest of <code>/dev</code>"
|
||||
],
|
||||
[
|
||||
"<code>/dev/fuse</code>",
|
||||
"The node and, when the profile needs it, the <code>fuse=1</code> feature",
|
||||
"FUSE alone does not publish mounts to other LXCs"
|
||||
],
|
||||
[
|
||||
"<code>/dev/net/tun</code>",
|
||||
"A <code>devN</code> entry at the same path inside the LXC",
|
||||
"The VPN or network configuration stays in the application"
|
||||
],
|
||||
[
|
||||
"<code>/dev/kvm</code>",
|
||||
"A validated <code>devN</code> entry",
|
||||
"Offered only when the contract asks for it"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
"The new image is verified layer by layer before the CT stops. A download that arrives damaged is fetched a second time; an image already cached that fails the check is downloaded again.",
|
||||
"Settings changed in Proxmox VE after the installation (memory, swap, cores, CPU limit, CPU priority, start with the node) are kept and carried into the new contract.",
|
||||
"The LAN interface that the Arr suite and multi-container applications add (<code>net1</code>) and the start order (<code>startup</code>) are given back to the new container as they are.",
|
||||
"A host directory is recognised by its path and by the filesystem it is on, not by the device name the host gives its disk, so a USB or network disk that comes back under another name is still the same directory. When its disk is not connected, the update stops before the CT is stopped and names the directory that is missing.",
|
||||
"Any other difference between the CT and its contract stops the update before the CT is stopped."
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,12 +28,30 @@
|
||||
{
|
||||
"steps": {
|
||||
"items": [
|
||||
{ "title": "Required paths", "body": "<code>/config</code>, <code>/data</code>, libraries, downloads and every volume the application declares are listed." },
|
||||
{ "title": "Location", "body": "Each path is placed on a disk of the container or on an existing host directory." },
|
||||
{ "title": "Storage and size", "body": "A container disk is created on a Proxmox VE storage with the size given. The default installation uses <code>local-lvm</code>; the advanced installation asks the storage of each path right before its size and proposes the one of the rootfs. It appears as <code>vm-VMID-disk-N</code> and is attached as <code>mpN</code>." },
|
||||
{ "title": "Host directory", "body": "A host directory is given by its path. A directory that does not exist is created, owned by the user the container maps." },
|
||||
{ "title": "Additional paths", "body": "More pairs of host path or volume and container path can be added before installing." },
|
||||
{ "title": "Summary", "body": "The complete mapping is shown before the CT is created and is stored in its instance contract." }
|
||||
{
|
||||
"title": "Required paths",
|
||||
"body": "<code>/config</code>, <code>/data</code>, libraries, downloads and every volume the application declares are listed."
|
||||
},
|
||||
{
|
||||
"title": "Location",
|
||||
"body": "Each path is placed on a disk of the container or on an existing host directory."
|
||||
},
|
||||
{
|
||||
"title": "Storage and size",
|
||||
"body": "A container disk is created on a Proxmox VE storage with the size given. The default installation asks once for the storage of the container and its data when the node has more than one; the advanced installation asks the storage of each path right before its size and proposes the one of the rootfs. It appears as <code>vm-VMID-disk-N</code> and is attached as <code>mpN</code>."
|
||||
},
|
||||
{
|
||||
"title": "Host directory",
|
||||
"body": "A host directory is given by its path. A directory that does not exist is created, owned by the user the container maps."
|
||||
},
|
||||
{
|
||||
"title": "Additional paths",
|
||||
"body": "More pairs of host path or volume and container path can be added before installing."
|
||||
},
|
||||
{
|
||||
"title": "Summary",
|
||||
"body": "The complete mapping is shown before the CT is created and is stored in its instance contract."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -45,20 +63,55 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Property", "Container disk", "Host directory"],
|
||||
"headers": [
|
||||
"Property",
|
||||
"Container disk",
|
||||
"Host directory"
|
||||
],
|
||||
"rows": [
|
||||
["In the configuration", "<code>mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G</code>", "<code>mpN: /mnt/oci-shared/media,mp=/data/media</code>"],
|
||||
["Container backup (vzdump)", "Included, with <code>backup=1</code>", "Not included"],
|
||||
["Size", "Fixed; grown with a resize of the mount point", "The free space of the host filesystem or dataset"],
|
||||
["Other containers", "Mounted only by its own container", "The same directory can be mounted in several containers"],
|
||||
["Snapshots and restore", "Managed by Proxmox VE together with the CT", "Managed on the host storage"],
|
||||
["Removing the application", "Deleted with the container", "Kept, with its content"],
|
||||
["Moving the CT to another node", "Moves with the CT", "The same path has to exist on the other node"]
|
||||
[
|
||||
"In the configuration",
|
||||
"<code>mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G</code>",
|
||||
"<code>mpN: /mnt/oci-shared/media,mp=/data/media</code>"
|
||||
],
|
||||
[
|
||||
"Container backup (vzdump)",
|
||||
"Included, with <code>backup=1</code>",
|
||||
"Not included"
|
||||
],
|
||||
[
|
||||
"Size",
|
||||
"Fixed; grown with a resize of the mount point",
|
||||
"The free space of the host filesystem or dataset"
|
||||
],
|
||||
[
|
||||
"Other containers",
|
||||
"Mounted only by its own container",
|
||||
"The same directory can be mounted in several containers"
|
||||
],
|
||||
[
|
||||
"Snapshots and restore",
|
||||
"Managed by Proxmox VE together with the CT",
|
||||
"Managed on the host storage"
|
||||
],
|
||||
[
|
||||
"Removing the application",
|
||||
"Deleted with the container",
|
||||
"Kept, with its content"
|
||||
],
|
||||
[
|
||||
"Moving the CT to another node",
|
||||
"Moves with the CT",
|
||||
"The same path has to exist on the other node"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"p": "The rootfs is reserved for the binaries and the content of the image. An update or a recreation replaces it without touching either kind of mount point."
|
||||
},
|
||||
{
|
||||
"p": "A host directory is mounted with everything the host has mounted inside it. A directory such as <code>/mnt/pve</code> shows its network shares, disks and datasets in the container, and what the host mounts or unmounts there later appears or disappears without restarting the container. A directory added as read-only is read-only together with what is mounted inside it when the container starts. A filesystem the host mounts afterwards arrives with the permissions the host mounts it with, and becomes read-only as well when the container is restarted."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -78,9 +131,18 @@
|
||||
{
|
||||
"flow": {
|
||||
"nodes": [
|
||||
{ "label": "Contract", "detail": "/config" },
|
||||
{ "label": "Location", "detail": "container disk\nor host directory" },
|
||||
{ "label": "LXC", "detail": "always /config\nfor the application" }
|
||||
{
|
||||
"label": "Contract",
|
||||
"detail": "/config"
|
||||
},
|
||||
{
|
||||
"label": "Location",
|
||||
"detail": "container disk\nor host directory"
|
||||
},
|
||||
{
|
||||
"label": "LXC",
|
||||
"detail": "always /config\nfor the application"
|
||||
}
|
||||
],
|
||||
"caption": "The application sees the path the image publishes; only where it is stored changes."
|
||||
}
|
||||
@@ -94,7 +156,10 @@
|
||||
{
|
||||
"mermaid": {
|
||||
"chartCode": "flowchart TB\n H[\"{{host}}<br/>/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent<br/>/data\"]\n H --> J[\"Jellyfin<br/>/data\"]\n H --> R[\"Radarr / Sonarr<br/>/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]",
|
||||
"labels": { "host": "Host directory", "config": "own configuration" }
|
||||
"labels": {
|
||||
"host": "Host directory",
|
||||
"config": "own configuration"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -110,10 +175,22 @@
|
||||
{
|
||||
"steps": {
|
||||
"items": [
|
||||
{ "title": "Container and remote", "body": "The VMID of the Rclone container, the exact name of the remote and, optionally, a path inside it." },
|
||||
{ "title": "Mount name and cache", "body": "The name of the mount and the VFS cache mode: <code>off</code>, <code>minimal</code>, <code>writes</code> or <code>full</code> (default)." },
|
||||
{ "title": "Published views", "body": "A common root, <code>/mnt/oci-shared</code> by default, holds a read/write view in <code>/mnt/oci-shared/remotes/NAME</code> and a read-only view in <code>/mnt/oci-shared/remotes-ro/NAME</code>." },
|
||||
{ "title": "Activation", "body": "After a confirmation, the CT is stopped, its start command and a Proxmox VE hookscript are set, and it is started again. The operation waits until both views are mounted on the host." }
|
||||
{
|
||||
"title": "Container and remote",
|
||||
"body": "The VMID of the Rclone container, the exact name of the remote and, optionally, a path inside it."
|
||||
},
|
||||
{
|
||||
"title": "Mount name and cache",
|
||||
"body": "The name of the mount and the VFS cache mode: <code>off</code>, <code>minimal</code>, <code>writes</code> or <code>full</code> (default)."
|
||||
},
|
||||
{
|
||||
"title": "Published views",
|
||||
"body": "A common root, <code>/mnt/oci-shared</code> by default, holds a read/write view in <code>/mnt/oci-shared/remotes/NAME</code> and a read-only view in <code>/mnt/oci-shared/remotes-ro/NAME</code>."
|
||||
},
|
||||
{
|
||||
"title": "Activation",
|
||||
"body": "After a confirmation, the CT is stopped, its start command and a Proxmox VE hookscript are set, and it is started again. The operation waits until both views are mounted on the host."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -133,17 +210,34 @@
|
||||
{
|
||||
"cards": {
|
||||
"items": [
|
||||
{ "icon": "network", "title": "Single application", "body": "Bridge and DHCP or a fixed CIDR address are chosen. The LXC has an address of its own and the summary shows the complete URLs of the services." },
|
||||
{ "icon": "waypoints", "title": "Multi-container application", "body": "A free subnet is found, a persistent private bridge is created and each member (application, database, cache) gets a fixed address on it." }
|
||||
{
|
||||
"icon": "network",
|
||||
"title": "Single application",
|
||||
"body": "Bridge and DHCP or a fixed CIDR address are chosen. The LXC has an address of its own and the summary shows the complete URLs of the services."
|
||||
},
|
||||
{
|
||||
"icon": "waypoints",
|
||||
"title": "Multi-container application",
|
||||
"body": "A free subnet is found, a persistent private bridge is created and each member (application, database, cache) gets a fixed address on it."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"flow": {
|
||||
"nodes": [
|
||||
{ "label": "LAN", "detail": "reachable address\nmain service only" },
|
||||
{ "label": "Main LXC", "detail": "web / API\nLAN + private network" },
|
||||
{ "label": "Private network", "detail": "PostgreSQL · Valkey · ML\nfixed addresses" }
|
||||
{
|
||||
"label": "LAN",
|
||||
"detail": "reachable address\nmain service only"
|
||||
},
|
||||
{
|
||||
"label": "Main LXC",
|
||||
"detail": "web / API\nLAN + private network"
|
||||
},
|
||||
{
|
||||
"label": "Private network",
|
||||
"detail": "PostgreSQL · Valkey · ML\nfixed addresses"
|
||||
}
|
||||
],
|
||||
"caption": "Dependencies talk over the private network and have no address on the LAN."
|
||||
}
|
||||
@@ -174,13 +268,37 @@
|
||||
"blocks": [
|
||||
{
|
||||
"table": {
|
||||
"headers": ["Element", "In the CT vzdump", "Where it is kept"],
|
||||
"headers": [
|
||||
"Element",
|
||||
"In the CT vzdump",
|
||||
"Where it is kept"
|
||||
],
|
||||
"rows": [
|
||||
["OCI rootfs", "Yes", "The CT backup; it can also be rebuilt from the image and the contract"],
|
||||
["Container disk with <code>backup=1</code>", "Yes", "The CT backup"],
|
||||
["Host directory", "No", "The backup of the host storage"],
|
||||
["Instance contract", "Yes, the copy carried inside the CT", "<code>/.proxmenux/oci-record.json</code> inside the CT; additional host and cluster copies are kept separately"],
|
||||
["Multi-container application", "Each member in its own backup", "The backups of every member, plus the stack contract and its bridge on the host"]
|
||||
[
|
||||
"OCI rootfs",
|
||||
"Yes",
|
||||
"The CT backup; it can also be rebuilt from the image and the contract"
|
||||
],
|
||||
[
|
||||
"Container disk with <code>backup=1</code>",
|
||||
"Yes",
|
||||
"The CT backup"
|
||||
],
|
||||
[
|
||||
"Host directory",
|
||||
"No",
|
||||
"The backup of the host storage"
|
||||
],
|
||||
[
|
||||
"Instance contract",
|
||||
"Yes, the copy carried inside the CT",
|
||||
"<code>/.proxmenux/oci-record.json</code> inside the CT; additional host and cluster copies are kept separately"
|
||||
],
|
||||
[
|
||||
"Multi-container application",
|
||||
"Each member in its own backup",
|
||||
"The backups of every member, plus the stack contract and its bridge on the host"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
"manualIntro": "The full sequence the script runs against each pool is replicable by hand. This is what you would type if you wanted to do the same thing on a single pool without ProxMenux:",
|
||||
"verifyTitle": "Verification and manual rollback",
|
||||
"oneShotTitle": "Need a one-shot trim instead of continuous?",
|
||||
"oneShotBody": "Some operators prefer a manual trim during off-peak hours rather than continuous trim activity. Disable autotrim and schedule a cron job that runs <code>zpool trim <pool></code> — same end goal, different cadence."
|
||||
"oneShotBody": "Some users prefer a manual trim during off-peak hours rather than continuous trim activity. Disable autotrim and schedule a cron job that runs <code>zpool trim <pool></code> — same end goal, different cadence."
|
||||
},
|
||||
"vzdump": {
|
||||
"heading": "Increase vzdump backup speed",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"why": {
|
||||
"heading": "Why this exists",
|
||||
"body": "Post-install optimizations occasionally get improvements — a better sysctl tuning, a stricter Logrotate limit, a new Log2RAM size heuristic, a fix for an edge case reported by a tester. Without an update path the operator had only two options: manually re-run the post-install script (which re-applies <em>every</em> optimization) or skip the improvement entirely. Apply Available Updates is the middle ground: a per-optimization, opt-in re-run that lifts only the versions that actually moved."
|
||||
"body": "Post-install optimizations occasionally get improvements — a better sysctl tuning, a stricter Logrotate limit, a new Log2RAM size heuristic, a fix for an edge case reported by a tester. Without an update path the user had only two options: manually re-run the post-install script (which re-applies <em>every</em> optimization) or skip the improvement entirely. Apply Available Updates is the middle ground: a per-optimization, opt-in re-run that lifts only the versions that actually moved."
|
||||
},
|
||||
"detection": {
|
||||
"heading": "How updates are detected",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Versioned registry on the host",
|
||||
"body": "When the operator applied each optimization, the corresponding <code>register_tool</code> call wrote the active version into <code>/usr/local/share/proxmenux/installed_tools.json</code>. That file is the source of truth for \"what is active on this host right now\"."
|
||||
"body": "When the user applied each optimization, the corresponding <code>register_tool</code> call wrote the active version into <code>/usr/local/share/proxmenux/installed_tools.json</code>. That file is the source of truth for \"what is active on this host right now\"."
|
||||
},
|
||||
{
|
||||
"title": "Monitor compares both sides",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"heading": "Why this option exists",
|
||||
"intro": "ProxMenux ships from two git branches:",
|
||||
"items": [
|
||||
"<strong>Stable</strong> — the <code>main</code> branch. What the README points at, what most operators want, with a slower release cadence and only changes that have already been through beta validation.",
|
||||
"<strong>Stable</strong> — the <code>main</code> branch. What the README points at, what most users want, with a slower release cadence and only changes that have already been through beta validation.",
|
||||
"<strong>Beta</strong> — the <code>develop</code> branch. Newer features and Monitor builds; carries the tag the next stable release will inherit. Some things may not work perfectly — that's the deal you sign up for and the way new features get tested before they ship."
|
||||
],
|
||||
"outro": "Hopping between channels used to require remembering the right installer URL, copying it in from the README, and editing <code>config.json</code> by hand if something got out of sync. The unified option does the whole flow as a single guided action."
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"unifiedCallout": {
|
||||
"title": "Why both directions are now one option",
|
||||
"body": "The previous “Deactivate Beta Program” only existed if the host had ever joined the beta. Operators wanting to <em>join</em> had to find the beta installer URL elsewhere; operators wanting to <em>leave</em> sometimes had to manually clean up <code>beta_*</code> metadata to be sure. The unified <strong>Change Release Channel</strong> option always shows up, makes the current channel explicit, and treats both directions as the same kind of operation — runnable, repeatable, idempotent."
|
||||
"body": "The previous “Deactivate Beta Program” only existed if the host had ever joined the beta. Users wanting to <em>join</em> had to find the beta installer URL elsewhere; users wanting to <em>leave</em> sometimes had to manually clean up <code>beta_*</code> metadata to be sure. The unified <strong>Change Release Channel</strong> option always shows up, makes the current channel explicit, and treats both directions as the same kind of operation — runnable, repeatable, idempotent."
|
||||
},
|
||||
"troubleshoot": {
|
||||
"heading": "Troubleshooting",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
"reset": {
|
||||
"heading": "Reset ProxMenux Monitor Password",
|
||||
"intro": "Recovery path for the operator who lost the dashboard password (or 2FA device). The option only appears when the Monitor is installed on the host. Selecting it:",
|
||||
"intro": "Recovery path for the user who lost the dashboard password (or 2FA device). The option only appears when the Monitor is installed on the host. Selecting it:",
|
||||
"items": [
|
||||
"Asks for confirmation with a yes/no dialog — the action is destructive for the login credentials.",
|
||||
"Backs up the existing <code>/root/.config/proxmenux-monitor/auth.json</code> to <code>auth.json.bak-<UTC timestamp></code> (mode <code>0600</code>) so the previous state can be restored manually if the reset was a mistake.",
|
||||
|
||||
Reference in New Issue
Block a user