update 1.2.4

This commit is contained in:
MacRimi
2026-07-22 16:39:34 +02:00
parent acebb1755d
commit 4f5ccc4933
5 changed files with 100 additions and 38 deletions

View File

@@ -3,7 +3,7 @@
### New version ProxMenux v1.2.4
This release adds two in-dashboard improvements — a one-click Proxmox update trigger from the Health Monitor and a mobile PWA install prompt — extends the Backups restore flow with automatic ZFS data-pool import, sharpens Log2RAM behaviour on hosts running Proxmox Backup Server as a service, hardens firewall bridge sysctl tuning across VM lifecycle events, narrows the ZFS ARC optimization to its own scope, makes persistent NIC naming idempotent across reruns, rebuilds DKMS drivers automatically when a new kernel is staged, keeps the Monitor terminal session intact when a ProxMenux update is available, and reinforces five notification templates plus three Health panel checks.
This release adds two in-dashboard improvements — a one-click Proxmox update trigger from the Health Monitor and a mobile PWA install prompt — extends the Backups restore flow with atomic pmxcfs (`config.db`) snapshots and automatic ZFS data-pool import, sharpens Log2RAM behaviour on hosts running Proxmox Backup Server as a service, hardens firewall bridge sysctl tuning across VM lifecycle events, narrows the ZFS ARC optimization to its own scope, makes persistent NIC naming idempotent across reruns, rebuilds DKMS drivers automatically when a new kernel is staged, keeps the Monitor terminal session intact when a ProxMenux update is available, and reinforces five notification templates plus three Health panel checks.
---
@@ -13,7 +13,7 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
- Runs the standard Proxmox update flow (`apt update` + `dist-upgrade` + post-update cleanup) in an in-dashboard terminal — no need to open a shell.
- Only appears when updates are pending; when the system is up to date the button stays hidden.
- On close, the Health Monitor forces a cache-busting refresh (`/api/health/full?refresh=1`) so the pending-update count and kernel row reflect the post-update state right away, instead of the pre-update value that the background cache had stored moments before the run.
- The underlying script is context-aware: on an already-configured production host it respects the operator's custom repositories (never disables enterprise/ceph, never deletes legacy sources, never purges alternate NTP, never force-installs zfsutils/chrony); on a bare host it lays down only the missing base repos. It also detects a newly installed kernel that isn't the running one and prompts for reboot at the end.
- The underlying script is context-aware: on an already-configured production host it respects the user's custom repositories (never disables enterprise/ceph, never deletes legacy sources, never purges alternate NTP, never force-installs zfsutils/chrony); on a bare host it lays down only the missing base repos. It also detects a newly installed kernel that isn't the running one and prompts for reboot at the end.
- During the upgrade, `service_fail` notifications for PVE services (pve-cluster, pveproxy, corosync…) are suppressed — their restart is a normal part of the upgrade cycle. Suppression extends 60 s past apt exit so the trailing restart events don't leak through.
---
@@ -29,7 +29,7 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
## 🔔 Notification content — five rendering refinements
- **Backup destination in title and body** — VM/CT backup emails and Telegram messages carry the storage / PBS target, so operators with several backup destinations can tell at a glance which one produced the event.
- **Backup destination in title and body** — VM/CT backup emails and Telegram messages carry the storage / PBS target, so users with several backup destinations can tell at a glance which one produced the event.
- **Migration bodies carry the real target node** — pulled from the PVE task log for `qmigrate` / `vzmigrate` events.
- **Snapshot bodies carry the real snapshot name** — pulled from the PVE task log for `qmsnapshot` / `vzsnapshot` events.
- **Generic `system_problem` notifications include the real reason** — PVE payload messages are surfaced as the notification body.
@@ -57,7 +57,7 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
- **The Monitor's WebSocket terminal now exposes `PROXMENUX_TERMINAL=monitor`** in the environment of every shell it opens, and every child inherits it. This gives `menu` (and any other flow that cares) a reliable, deterministic way to tell that the current session lives inside the Monitor process — a session that would be cut mid-install if the Monitor service was restarted.
- **`menu` update prompt** — when a new ProxMenux version is available and the session is running inside the Monitor terminal, the classic yes/no update prompt is replaced by an informational msgbox. The msgbox names the new version and shows the canonical one-liner (`bash -c "$(wget -qLO - …)"`) to run the update from SSH or the Proxmox host console. Because the flow has already decided the in-terminal update path is unsafe (see the [msgbox-ack rule](memory/feedback_whiptail_msgbox_ack.md)), there's a single OK button — no yes/no that could trigger the destructive update by accident.
- **Settings → Release Channel** — the same guard is applied in `config_menu.sh`'s `apply_release_channel()`. Selecting Stable ↔ Beta from the Monitor terminal shows an informational msgbox with the exact `wget` one-liner for the target channel (using the same URL the flow would have downloaded itself) and returns to the menu instead of running the installer in place.
- **After OK, both flows continue normally**. The operator keeps using ProxMenux from the same terminal without restrictions; only the destructive step is routed elsewhere. There is no lockdown and no forced action.
- **After OK, both flows continue normally**. The user keeps using ProxMenux from the same terminal without restrictions; only the destructive step is routed elsewhere. There is no lockdown and no forced action.
- **SSH sessions, the Proxmox host console, and any environment where `PROXMENUX_TERMINAL` isn't `monitor` keep the previous behaviour** and can update or switch channels as always. The change only affects the case where doing it in place would break the running session.
- **Bootstrap note**: because `PROXMENUX_TERMINAL=monitor` is added by the AppImage this release ships, the guard only starts protecting sessions once the host is on 1.2.4 or newer. The very first update to 1.2.4, if triggered from the Monitor terminal, can still hit the old behaviour — from 1.2.4 forward the guard is in place.
@@ -66,16 +66,16 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
## 🔧 Update flow — DKMS drivers rebuilt when a new kernel lands
- **After `apt full-upgrade` stages a kernel newer than the one currently running, `update-pve-safe.sh` now rebuilds ProxMenux-installed DKMS drivers against the new kernel.** The Update Now button in the Health Monitor and the `utilities/proxmox_update.sh` CLI both delegate to `update-pve-safe.sh`, so both routes gain the behaviour. The step reads `components_status.json`, cross-references the DKMS-managed components ProxMenux tracks (`nvidia_driver`, `coral_driver`), installs the matching kernel headers (`proxmox-headers-<newkver>` or `pve-headers-<newkver>`) if they aren't already present, and runs `dkms autoinstall -k <newkver>`. Then it verifies via `dkms status` that each expected module (`gasket` for Coral, `nvidia` for the NVIDIA driver) actually reached `installed` state for the new kernel — if any module didn't, it falls back to each installer's `--auto-reinstall` path.
- **A whiptail msgbox announces the rebuild before it runs.** Single OK button — no yes/no. Names the incoming kernel version and lists the DKMS components that are going to be rebuilt, so the operator sees exactly what's about to happen. Because leaving DKMS drivers unbuilt would leave the system with a working kernel but non-functional TPU / GPU at boot, this is transparency, not a decision — pressing OK acknowledges the follow-up work and the flow proceeds. Non-interactive invocations (cron, headless batch, missing whiptail) skip the msgbox and log the same information.
- **A whiptail msgbox announces the rebuild before it runs.** Single OK button — no yes/no. Names the incoming kernel version and lists the DKMS components that are going to be rebuilt, so the user sees exactly what's about to happen. Because leaving DKMS drivers unbuilt would leave the system with a working kernel but non-functional TPU / GPU at boot, this is transparency, not a decision — pressing OK acknowledges the follow-up work and the flow proceeds. Non-interactive invocations (cron, headless batch, missing whiptail) skip the msgbox and log the same information.
- **Only components already registered as `installed` in `components_status.json` are considered.** A host with no ProxMenux-managed DKMS drivers sees no msgbox and no rebuild step. Hosts that never ran the Coral or NVIDIA installer are unaffected.
- **Failure to rebuild does not abort the update.** If a DKMS module can't be rebuilt against the new kernel (upstream API break, missing dependency), the update flow completes normally, the specific components that failed are named in the summary, and the operator can re-run their installer manually after reboot. The step is best-effort by design — a kernel/driver mismatch is an upstream problem, not something the update flow should fail on.
- **Failure to rebuild does not abort the update.** If a DKMS module can't be rebuilt against the new kernel (upstream API break, missing dependency), the update flow completes normally, the specific components that failed are named in the summary, and the user can re-run their installer manually after reboot. The step is best-effort by design — a kernel/driver mismatch is an upstream problem, not something the update flow should fail on.
- Shared helper `pmx_rebuild_dkms_after_kernel` lives in `scripts/global/utils-install-functions.sh`, so future updaters or CLI utilities can pick it up with a one-line call.
---
## 🔌 Post-install — Persistent NIC naming becomes idempotent
- **ProxMenux-owned `.link` files now carry a distinctive filename prefix and internal marker.** Files are written as `10-proxmenux-<iface>.link` and the first line of every file is `# Managed by ProxMenux — do not edit`. Both are checked by the reconciliation and uninstall paths before touching a file, so anything the operator wrote by hand or that came from another package is safe.
- **ProxMenux-owned `.link` files now carry a distinctive filename prefix and internal marker.** Files are written as `10-proxmenux-<iface>.link` and the first line of every file is `# Managed by ProxMenux — do not edit`. Both are checked by the reconciliation and uninstall paths before touching a file, so anything the user wrote by hand or that came from another package is safe.
- **Reruns of `setup_persistent_network` reconcile ProxMenux entries.** Every invocation walks the existing `10-proxmenux-*.link` files, extracts the `MACAddress=` value, compares it against the MACs currently present under `/sys/class/net/`, and removes only the ProxMenux-owned entries whose MAC is no longer there. Hardware replacements, NIC swaps and hardware migrations stop leaving orphan mappings behind on every rerun.
- **Legacy 1.0-format files (`10-<iface>.link` written by the previous revision) are migrated on the first run of the new function.** If the file matches the exact template the 1.0 code used to write (two sections, `MACAddress=` + `Name=`, nothing else), it's removed and replaced with the new `10-proxmenux-<iface>.link` in one step. Any file that doesn't match the template exactly is left alone.
- **The uninstall path (`uninstall_persistent_network`) now only removes files that carry both the `10-proxmenux-` filename prefix and the marker on the first line.** The previous `rm -f /etc/systemd/network/*.link` blanket sweep is gone — user-authored `.link` files stay in place regardless of their filename.
@@ -86,7 +86,7 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
## 🧮 Post-install — ZFS ARC optimization narrowed to its scope
- **`optimize_zfs_arc` now sets only `zfs_arc_max`.** The function writes a single line to `/etc/modprobe.d/99-zfsarc.conf`: `options zfs zfs_arc_max=<cap>`. `zfs_arc_min` stays at the OpenZFS default (auto-calculated as the larger of 32 MiB and ~1/32 of RAM), and L2ARC (`l2arc_noprefetch`, `l2arc_write_max`) and TXG (`zfs_txg_timeout`) tunables — which are outside the scope of an ARC optimization — are left at their OpenZFS defaults unless the operator configures them elsewhere.
- **`optimize_zfs_arc` now sets only `zfs_arc_max`.** The function writes a single line to `/etc/modprobe.d/99-zfsarc.conf`: `options zfs zfs_arc_max=<cap>`. `zfs_arc_min` stays at the OpenZFS default (auto-calculated as the larger of 32 MiB and ~1/32 of RAM), and L2ARC (`l2arc_noprefetch`, `l2arc_write_max`) and TXG (`zfs_txg_timeout`) tunables — which are outside the scope of an ARC optimization — are left at their OpenZFS defaults unless the user configures them elsewhere.
- **The initramfs is now regenerated after writing the config.** On ZFS-on-root systems the ZFS module loads from the initramfs before the running system reads `/etc/modprobe.d/`, so a plain reboot wasn't enough for the new cap to take effect. `update-initramfs -u -k all` runs right after the file is written, plus `proxmox-boot-tool refresh` on systemd-boot hosts, so the value is picked up at the next boot instead of being shadowed by the initramfs's stale copy.
- **The function is guarded on the presence of a live ZFS pool** (`zpool list` check) so it becomes a no-op on hosts that don't use ZFS.
- **Cap values use clean binary sizes**: 512 MiB up to 16 GB RAM, 1 GiB up to 32 GB, RAM/8 above that — with a floor of 512 MiB so a bad memory reading never leaves an unusably small ARC.
@@ -109,9 +109,11 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t
- **Upstream `log2ram` script patched to `rsync -aXv --no-acls` right after `install.sh`.** Both installers rewrite the call in place with a `sed` guarded by `grep -q` (backup at `.proxmenux.bak`, no-op if a future upstream release already dropped `-A`). Extended attributes (`-X`) are preserved. Result: `log2ram write` finishes cleanly on `/var/log.hdd` filesystems that don't accept POSIX ACLs (ZFS with `acltype=off`, ext4 mounted without the `acl` option) — no more `set_acl: Operation not supported` / exit 23 messages.
- **Emergency block of `log2ram-check.sh` rotates PBS logs before truncating.** When `/var/log` crosses the 92% threshold, the auto-sync script now runs `logrotate -f /etc/logrotate.d/proxmox-backup-api` (only if the rule file exists) *before* truncating `pveproxy/access.log`, `pveproxy/error.log` and `pveam.log`. Recent PBS access/auth history is preserved in the rotated `.gz` files instead of being lost. Both `install_log2ram_auto` and `configure_log2ram` bumped `FUNC_VERSION` 1.2 → 1.3, and the embedded `log2ram-check.sh` header comment bumped v1.2 → v1.3.
## 🗄 Backup restore — ZFS data pools imported automatically
## 🗄 Backup restore — pmxcfs snapshot + ZFS data pools
- **Separate ZFS data pools listed in the backup are now imported automatically at restore time.** The new `_rs_import_data_pools` step runs after config apply, walks `storage_inventory.zfs_pools[]`, skips the root pool (already mounted by the system), and issues `zpool import <name>` for every non-root pool whose disks are all present on this host. When ZFS rejects the import as *foreign* — the typical case after a fresh install regrabs the pool label with a new `hostid` — the step retries with `-f` and reports the pool as forced so the operator has trace. Pools missing any disk are skipped with a clear warning rather than imported degraded. Together this closes the common case where `zfs-import-scan.service` failed at boot after a fresh install and left the separate data pool unavailable until `zpool import -f` was run manually.
- **`/var/lib/pve-cluster/config.db` is now captured with `sqlite3 .backup`.** pmxcfs (`/etc/pve`) is served by `pve-cluster` from that SQLite store, so a plain rsync of the raw file with the service running can catch it mid-WAL checkpoint and land in the archive as an inconsistent copy. `hb_prepare_staging` now runs `sqlite3 /var/lib/pve-cluster/config.db ".backup '$staging/…/config.db'"` before the general rsync — the canonical way (documented by Proxmox) to snapshot the store consistently while `pve-cluster` keeps serving traffic, with zero downtime for the cluster. The general rsync of `/var/lib/pve-cluster` now excludes `config.db`, `config.db-wal` and `config.db-shm` so nothing overwrites the atomic dump. Hosts without `sqlite3` fall back to a raw copy named `config.db.raw-fallback`, which the recovery helper promotes to `config.db` before starting `pve-cluster`. Metadata records which path was used via `pmxcfs_config_db=sqlite_backup|raw_fallback` in `metadata/run_info.env` for trace. The restore path continues to use the canonical `systemctl stop pve-cluster → cp → systemctl start pve-cluster` pattern (`apply_pending_restore.sh` and the standalone recovery helper written next to every extracted cluster dir), so the DB the user brings back is now guaranteed consistent instead of a raw file copy of state in flight.
- **Separate ZFS data pools listed in the backup are now imported automatically at restore time.** The new `_rs_import_data_pools` step runs after config apply, walks `storage_inventory.zfs_pools[]`, skips the root pool (already mounted by the system), and issues `zpool import <name>` for every non-root pool whose disks are all present on this host. When ZFS rejects the import as *foreign* — the typical case after a fresh install regrabs the pool label with a new `hostid` — the step retries with `-f` and reports the pool as forced so the user has trace. Pools missing any disk are skipped with a clear warning rather than imported degraded. Together this closes the common case where `zfs-import-scan.service` failed at boot after a fresh install and left the separate data pool unavailable until `zpool import -f` was run manually.
- **The auto-import result persists to the post-restore progress card.** The step writes a `data_pools_import` section into `/var/lib/proxmenux/restore-state.json` (the same JSON the Backups-tab card polls) and a raw log at `/var/log/proxmenux/restore-datapools-<timestamp>.log`. The Backups tab card renders a dedicated block inside Details with five color-coded rows (Imported / Forced / Skipped partial / Skipped missing / Failed) so the summary stays consultable after the restore terminal is closed, and the entry is preserved in the run's history for later review.
- **ZFS pools created with `by-partuuid` or raw `/dev/sdX` are recognised by the disk-presence check.** The auto-import step and `validate_storage.sh` treat `devices_by_id` entries that start with `/` as absolute paths and only prepend `/dev/disk/by-id/` to bare basenames, so pools built against partition UUIDs or a raw block device are detected as present when their disks are on the host.
- **`/etc/systemd/network` added to the default backup paths.** That directory holds systemd `.link` files that pin NIC names to their MAC across kernel updates and reinstalls — `setup_persistent_network` in the post_install writes them for every physical interface, and users can drop their own to rename a NIC to something meaningful. Preserving them across a fresh-install restore keeps the source host's NIC naming policy intact on the target, so `/etc/network/interfaces` entries that reference custom NIC names continue to resolve after the restore.
@@ -146,7 +148,7 @@ Stable consolidation of the **v1.2.2.x beta cycle** (v1.2.2.1 → v1.2.2.2 → v
- **PBS encryption with recovery blob**: encrypted backups store a passphrase-wrapped copy of the keyfile as a `-keyrecovery` group next to each backup, so a new Proxmox install can always get the key back with the user's passphrase.
- **Direction-aware restore**: reapplies IOMMU / VFIO / GRUB tunings on cross-kernel jumps, protects critical packages from cascade-remove, auto-remaps NICs after a motherboard swap.
- **Live post-restore progress card**: after the reboot, the Backups tab shows a real-time card with step-by-step milestones, per-component status (NVIDIA, Intel GPU tools, Coral, AMD tools) and a log tail with an Issues-only filter. Past restores are archived and browsable.
- **PBS keyfile management inline in the Monitor**: each PBS destination row exposes Download / Upload / Delete for the keyfile plus a Yes/No + passphrase + contextual Apply toggle for the escrow. When the installed keyfile does not match the backup's manifest, View contents / Download / Restore now show a structured amber panel with the required fingerprint so the operator knows which keyfile to import.
- **PBS keyfile management inline in the Monitor**: each PBS destination row exposes Download / Upload / Delete for the keyfile plus a Yes/No + passphrase + contextual Apply toggle for the escrow. When the installed keyfile does not match the backup's manifest, View contents / Download / Restore now show a structured amber panel with the required fingerprint so the user knows which keyfile to import.
---
@@ -190,7 +192,7 @@ For users who do **not** use an AI enhancement agent, the templated body now add
- **USB-NVMe / USB-SATA SMART on `removable=0` enclosures** — enclosures reporting `removable=0` (ASMedia, JMicron, Realtek, ASM105x) now walk sysfs to detect USB attachment, so `-d snt*` pass-through is tried and the drive's real model, serial, temperature, power-on hours and health surface. Temperature history sampler picks up the same fix.
- **PBS encryption prompt reworked** to a single explicit *Encrypt this backup?* Yes/No — nothing is uploaded to PBS unless the answer is Yes. Only when a keyfile is not yet installed does a second dialog ask whether to generate a new one or import an existing one. Cancelling never leaves a phantom keyfile behind.
- **Attached scheduled backups now inherit retention on every run** — jobs attached to a PVE vzdump parent re-read the parent's `prune-backups` config at each run and rewrite `KEEP_*` accordingly. Previously frozen to the value at job creation time.
- **Installer no longer auto-relaunches `menu` after an update** — the `exec MENU_SCRIPT` at the tail of the update path triggered *"line: syntax"* errors when bash tried to read the just-rewritten `/usr/local/bin/menu` under its feet. Flow now exits cleanly; operator types `menu` when ready. `change_release_channel` in Settings unaffected.
- **Installer no longer auto-relaunches `menu` after an update** — the `exec MENU_SCRIPT` at the tail of the update path triggered *"line: syntax"* errors when bash tried to read the just-rewritten `/usr/local/bin/menu` under its feet. Flow now exits cleanly; user types `menu` when ready. `change_release_channel` in Settings unaffected.
- **PBS restore listing broken on Proxmox 9 / jq 1.7** — `hb_pbs_list_snapshots` switched from the prefix form `and not (...)` (rejected by jq 1.7) to the postfix form `and ((...) | not)` (accepted by both jq 1.6 and 1.7). Silent stderr redirect removed so future parse errors surface.
- **`run_scheduled_backup.sh` no longer crashes when `LANGUAGE` is unset** — cron / systemd invocations now load language + initialize the translation cache before sourcing utility functions that require it.
- **Local archive restore prompts no longer freeze silently** — `hb_prompt_restore_source_dir` and `hb_prompt_local_archive` use the fd-9 TTY handoff already applied elsewhere.
@@ -237,7 +239,7 @@ Special thanks to the community members who shaped this release with concrete de
- **[@JF_Car](https://github.com/JF_Car)** — proposed the tree layout for the new Network Flow diagram so it reads correctly on mobile devices.
- **[@ghosthvj](https://github.com/ghosthvj)** — contributed the design for the new **Physical Disks** and **Physical Interfaces** cards.
- **[@riglesias](https://github.com/riglesias)**, **[@princo56](https://github.com/princo56)** and **[@jonatanc](https://github.com/jonatanc)** — tested the beta cycle end-to-end and provided the suggestions that closed most of the operator-visible gaps.
- **[@riglesias](https://github.com/riglesias)**, **[@princo56](https://github.com/princo56)** and **[@jonatanc](https://github.com/jonatanc)** — tested the beta cycle end-to-end and provided the suggestions that closed most of the user-visible gaps.
And to every user who opened an issue, commented in [GitHub Discussions](https://github.com/MacRimi/ProxMenux/discussions), reported a bug on the community channel, or told us what worked and what didn't on their hardware — most internal fixes in this release started as one of those reports. Keep them coming.
@@ -247,13 +249,13 @@ And to every user who opened an issue, commented in [GitHub Discussions](https:/
### New version ProxMenux v1.2.2 — *Stable consolidation of the v1.2.1.x cycle*
Stable release that brings the four prereleases of the **v1.2.1.x** cycle to the main channel in one move. The work over those four betas centred on three themes: making the Health Monitor genuinely configurable instead of just observable (per-category thresholds, per-event dismiss durations, an audit log of active suppressions), expanding the notification stack to cover roughly 80 services through Apprise while persisting events across Quiet Hours, and turning the Monitor process itself into a quieter, more predictable system citizen on idle hosts. On top of those, this release lands automatic upgrade detection for LXC containers, an end-to-end rewrite of the Coral TPU installer with the latest upstream drivers, and a long list of operator-visible fixes — HTTPS terminal handshake, kernel-update detection on PVE 9.x, NVIDIA installer flow on Alpine LXC, mixed-GPU passthrough audio companion handling, and several runtime optimizations on the Monitor scanning loops. Five direct code contributions from the community ship alongside ([@jcastro](https://github.com/jcastro) ×5, [@pespinel](https://github.com/pespinel) ×1) and the GPU passthrough work was driven by [@ghosthvj](https://github.com/ghosthvj)'s detailed field reports — see the Acknowledgments at the end.
Stable release that brings the four prereleases of the **v1.2.1.x** cycle to the main channel in one move. The work over those four betas centred on three themes: making the Health Monitor genuinely configurable instead of just observable (per-category thresholds, per-event dismiss durations, an audit log of active suppressions), expanding the notification stack to cover roughly 80 services through Apprise while persisting events across Quiet Hours, and turning the Monitor process itself into a quieter, more predictable system citizen on idle hosts. On top of those, this release lands automatic upgrade detection for LXC containers, an end-to-end rewrite of the Coral TPU installer with the latest upstream drivers, and a long list of user-visible fixes — HTTPS terminal handshake, kernel-update detection on PVE 9.x, NVIDIA installer flow on Alpine LXC, mixed-GPU passthrough audio companion handling, and several runtime optimizations on the Monitor scanning loops. Five direct code contributions from the community ship alongside ([@jcastro](https://github.com/jcastro) ×5, [@pespinel](https://github.com/pespinel) ×1) and the GPU passthrough work was driven by [@ghosthvj](https://github.com/ghosthvj)'s detailed field reports — see the Acknowledgments at the end.
---
## 🩺 Health Monitor — Configurable, Granular, Auditable
Three coupled pieces that together let the operator tune the Health Monitor to the actual envelope of their host instead of working around its defaults, and to manage dismisses with the same fine-grained control they already have over the rest of the dashboard.
Three coupled pieces that together let the user tune the Health Monitor to the actual envelope of their host instead of working around its defaults, and to manage dismisses with the same fine-grained control they already have over the rest of the dashboard.
### Per-category Warning / Critical thresholds
@@ -273,7 +275,7 @@ The *Dismiss* button on each Health Monitor alert now opens a small dropdown wit
- **7 days** — handy for a temporary condition you don't want to hear about during a week-long migration
- **Permanently** — silences this specific `error_key` indefinitely
Permanent dismisses persist with `suppression_hours = -1` in the persistence DB, never re-emit, never re-notify and are marked with a distinct amber **Permanent** badge in the Health Monitor so the operator always knows which alerts are intentionally silenced. The backend infrastructure for the permanent sentinel already existed — the UI just lacked a way to set it. The API contract is small and backwards-compatible: `POST /api/health/acknowledge` accepts an optional `suppression_hours` body field (positive integer for hours, `-1` for permanent); omitting it preserves the previous behaviour and uses the category's configured suppression. A second new endpoint `POST /api/health/un-acknowledge {error_key}` clears a previously-recorded acknowledgment so the alert becomes eligible to fire again — used by the Active Suppressions panel below.
Permanent dismisses persist with `suppression_hours = -1` in the persistence DB, never re-emit, never re-notify and are marked with a distinct amber **Permanent** badge in the Health Monitor so the user always knows which alerts are intentionally silenced. The backend infrastructure for the permanent sentinel already existed — the UI just lacked a way to set it. The API contract is small and backwards-compatible: `POST /api/health/acknowledge` accepts an optional `suppression_hours` body field (positive integer for hours, `-1` for permanent); omitting it preserves the previous behaviour and uses the category's configured suppression. A second new endpoint `POST /api/health/un-acknowledge {error_key}` clears a previously-recorded acknowledgment so the alert becomes eligible to fire again — used by the Active Suppressions panel below.
### Active Suppressions panel in Settings
@@ -301,7 +303,7 @@ Three reliability fixes ship alongside, all surfaced after the initial beta roll
2. **Backend whitelist regression** that rejected Apprise with HTTP 400. The notifications-test validator's hard-coded channel set (`{telegram, gotify, discord, email, all}`) was missing `apprise`, so every Apprise test or send returned `400 Invalid channel` before the library was even invoked. The whitelist is now derived live from `notification_channels.CHANNEL_TYPES`, so adding a new channel implementation in the future cannot silently regress this validator again.
3. **Opaque error reporting** when the destination returned a non-2xx response. When a destination (`jsons://`, `ntfy://`, `slack://`, …) rejected the payload, the operator only saw a generic *"Apprise rejected the notification (transport failure)"* message. The channel now captures Apprise's internal logger during `notify()` and surfaces the real HTTP status code plus the destination's response body (capped at 300 chars) — so a beta tester debugging a custom webhook can immediately see whether the upstream server is rejecting their payload schema.
3. **Opaque error reporting** when the destination returned a non-2xx response. When a destination (`jsons://`, `ntfy://`, `slack://`, …) rejected the payload, the user only saw a generic *"Apprise rejected the notification (transport failure)"* message. The channel now captures Apprise's internal logger during `notify()` and surfaces the real HTTP status code plus the destination's response body (capped at 300 chars) — so a beta tester debugging a custom webhook can immediately see whether the upstream server is rejecting their payload schema.
---
@@ -338,7 +340,7 @@ The mount monitor used to call `lxc-info -n <vmid> -p` for every running CT just
## 🔌 HTTPS Terminal Handshake
Every terminal modal in the Monitor (dashboard terminal, LXC terminal, script terminal) used to fail with *WebSocket connection error* on hosts where HTTPS was enabled. The root cause was specific to the `gevent + SSL` path: the gevent-websocket `WebSocketHandler` was stacked on top of flask-sock's protocol implementation, so the server emitted **two** consecutive `HTTP/1.1 101 Switching Protocols` headers and the browser closed the connection as a corrupt frame. Dropping the explicit `handler_class=WebSocketHandler` argument restores a single 101 response and the handshake completes normally. The fix is invisible to operators running on plain HTTP — they were unaffected — but unblocks every HTTPS-fronted install (reverse proxies, certificate-managed deployments, anything behind nginx/Traefik).
Every terminal modal in the Monitor (dashboard terminal, LXC terminal, script terminal) used to fail with *WebSocket connection error* on hosts where HTTPS was enabled. The root cause was specific to the `gevent + SSL` path: the gevent-websocket `WebSocketHandler` was stacked on top of flask-sock's protocol implementation, so the server emitted **two** consecutive `HTTP/1.1 101 Switching Protocols` headers and the browser closed the connection as a corrupt frame. Dropping the explicit `handler_class=WebSocketHandler` argument restores a single 101 response and the handshake completes normally. The fix is invisible to users running on plain HTTP — they were unaffected — but unblocks every HTTPS-fronted install (reverse proxies, certificate-managed deployments, anything behind nginx/Traefik).
Additionally, the terminal panel used to lose its WebSocket connection when the user enabled the browser's auto-translate feature (Chrome / Edge / Safari "translate this page" prompts). The translator moves DOM nodes that React still holds refs to, and the WebSocket React component breaks because its container ref points to a moved node. Added `translate="no"` on the terminal container divs so the translator skips the embedded tty entirely — translations on the rest of the page still work.
@@ -352,7 +354,7 @@ On Proxmox VE 9.x hosts, the *System Updates → Kernel / PVE* row used to repor
2. **Dry-run switched from `apt-get upgrade --dry-run` to `apt-get dist-upgrade --dry-run`**. PVE 9 ships kernel updates packaged as new installs (not as straight upgrades of an existing package), and the plain `upgrade --dry-run` does not consider new installs at all. `dist-upgrade --dry-run` does.
3. **Running-kernel detection** now reads `uname -r` and flags an update as a *running-kernel update* when the package matches the running release exactly or its branch meta-package (e.g. `proxmox-kernel-6.14` for a host on `6.14.11-4-pve`). The row text distinguishes *"Running kernel update available (reboot required)"* from *"N kernel update(s) available (none for running kernel)"* so the operator knows whether they need to reboot or just install.
3. **Running-kernel detection** now reads `uname -r` and flags an update as a *running-kernel update* when the package matches the running release exactly or its branch meta-package (e.g. `proxmox-kernel-6.14` for a host on `6.14.11-4-pve`). The row text distinguishes *"Running kernel update available (reboot required)"* from *"N kernel update(s) available (none for running kernel)"* so the user knows whether they need to reboot or just install.
---
@@ -382,9 +384,9 @@ New documentation pages cover the **Active Suppressions** section in the Setting
- **Post-install function update detection** — the Monitor tracks installed ProxMenux optimizations (Log2Ram, Memory Settings, System Limits, Logrotate, …) and notifies when a newer version is available, with one-click apply from Settings.
- **Secure Gateway (Tailscale) update flow** — one-click Tailscale update from Settings with Last-checked / Installed / Latest indicators and notification when a new version is published.
- **Helper-Scripts menu** — richer context and useful information for each entry, making it easier to know what every script does before running it.
- **Burst aggregation wording** — burst summaries now report only the *additional* events that arrived after the initial individual alert, so the operator no longer sees the first event counted twice.
- **Burst aggregation wording** — burst summaries now report only the *additional* events that arrived after the initial individual alert, so the user no longer sees the first event counted twice.
- **Known-error classifier** — word-boundary regex on ATA / UNC patterns so kernel messages like `nvidia_uvm:FatalError` are no longer misclassified as ATA cable issues.
- **VM / CT control errors** — failed start / stop / restart now surfaces the real `pvesh` stderr (e.g. *"no space left on device"*) in the UI toast and fires a `vm_fail` / `ct_fail` notification, instead of the bare 500 INTERNAL SERVER ERROR the operator used to see.
- **VM / CT control errors** — failed start / stop / restart now surfaces the real `pvesh` stderr (e.g. *"no space left on device"*) in the UI toast and fires a `vm_fail` / `ct_fail` notification, instead of the bare 500 INTERNAL SERVER ERROR the user used to see.
- **log2ram apply path** — the auto / update flow now restarts log2ram after writing the new size, so a configured `512M` actually takes effect on the running tmpfs without a manual restart.
- **PVE webhook URL** — the notification webhook now follows the active SSL state automatically, switching between `http://` and `https://` when you toggle HTTPS in the panel.
- **Frontend 401 cascade** — the login screen no longer swallows a 401 forever after a brief stale-token state; the dedup flag is cleared on mount and on successful login.

View File

@@ -221,7 +221,17 @@ read -r -p "Type YES to continue: " ans
systemctl stop pve-cluster || true
[[ -d "\$RECOVERY_ROOT/etc/pve" ]] && mkdir -p /etc/pve && cp -a "\$RECOVERY_ROOT/etc/pve/." /etc/pve/ || true
[[ -d "\$RECOVERY_ROOT/var/lib/pve-cluster" ]] && mkdir -p /var/lib/pve-cluster && cp -a "\$RECOVERY_ROOT/var/lib/pve-cluster/." /var/lib/pve-cluster/ || true
if [[ -d "\$RECOVERY_ROOT/var/lib/pve-cluster" ]]; then
mkdir -p /var/lib/pve-cluster
cp -a "\$RECOVERY_ROOT/var/lib/pve-cluster/." /var/lib/pve-cluster/ || true
# If the backup only carried the raw-fallback (no sqlite3 dump),
# promote it to config.db so pve-cluster picks it up on start.
if [[ ! -f /var/lib/pve-cluster/config.db && -f /var/lib/pve-cluster/config.db.raw-fallback ]]; then
mv -f /var/lib/pve-cluster/config.db.raw-fallback /var/lib/pve-cluster/config.db
else
rm -f /var/lib/pve-cluster/config.db.raw-fallback
fi
fi
systemctl start pve-cluster || true
echo "Cluster recovery finished."
EOF

View File

@@ -1648,6 +1648,13 @@ fi
if [[ -d "\$RECOVERY_ROOT/var/lib/pve-cluster" ]]; then
mkdir -p /var/lib/pve-cluster
cp -a "\$RECOVERY_ROOT/var/lib/pve-cluster/." /var/lib/pve-cluster/ || true
# If the backup only carried the raw-fallback (no sqlite3 dump), promote
# it to config.db so pve-cluster picks it up on start.
if [[ ! -f /var/lib/pve-cluster/config.db && -f /var/lib/pve-cluster/config.db.raw-fallback ]]; then
mv -f /var/lib/pve-cluster/config.db.raw-fallback /var/lib/pve-cluster/config.db
else
rm -f /var/lib/pve-cluster/config.db.raw-fallback
fi
fi
systemctl start pve-cluster || true

View File

@@ -525,6 +525,30 @@ hb_prepare_staging() {
: > "$selected_file"
: > "$missing_file"
# pmxcfs (/etc/pve) is served from this SQLite DB with pve-cluster
# running. A plain rsync of the raw file can catch it mid-WAL
# checkpoint. sqlite3's `.backup` is the canonical way to grab a
# consistent snapshot with the DB in use, matching Proxmox's own
# documented advice. The raw file is also kept as `.raw-fallback`
# for environments without sqlite3.
if [[ -f /var/lib/pve-cluster/config.db ]]; then
mkdir -p "$staging_root/rootfs/var/lib/pve-cluster"
if command -v sqlite3 >/dev/null 2>&1; then
if sqlite3 /var/lib/pve-cluster/config.db \
".backup '$staging_root/rootfs/var/lib/pve-cluster/config.db'" 2>/dev/null; then
echo "pmxcfs_config_db=sqlite_backup" >> "$staging_root/metadata/run_info.env.tmp"
else
cp -a /var/lib/pve-cluster/config.db \
"$staging_root/rootfs/var/lib/pve-cluster/config.db.raw-fallback" 2>/dev/null || true
echo "pmxcfs_config_db=raw_fallback" >> "$staging_root/metadata/run_info.env.tmp"
fi
else
cp -a /var/lib/pve-cluster/config.db \
"$staging_root/rootfs/var/lib/pve-cluster/config.db.raw-fallback" 2>/dev/null || true
echo "pmxcfs_config_db=raw_fallback" >> "$staging_root/metadata/run_info.env.tmp"
fi
fi
local p rel target
for p in "${paths[@]}"; do
rel="${p#/}"
@@ -541,6 +565,19 @@ hb_prepare_staging() {
--exclude "*.log"
)
# /var/lib/pve-cluster: skip the raw config.db and its WAL/SHM
# sidecars — they were already captured atomically above via
# sqlite3 .backup (or as raw-fallback when sqlite3 isn't
# available). Everything else in the directory (backup subdir,
# auxiliary state) is safe to rsync live.
if [[ "$rel" == "var/lib/pve-cluster" || "$rel" == "var/lib/pve-cluster/"* ]]; then
rsync_opts+=(
--exclude "config.db"
--exclude "config.db-wal"
--exclude "config.db-shm"
)
fi
# /root is included by default for easier recovery, but avoid volatile/sensitive noise.
if [[ "$rel" == "root" || "$rel" == "root/"* ]]; then
rsync_opts+=(
@@ -588,7 +625,11 @@ hb_prepare_staging() {
echo "generated_at=$(date -Iseconds)"
echo "hostname=$(hostname)"
echo "kernel=$(uname -r)"
if [[ -f "$meta/run_info.env.tmp" ]]; then
cat "$meta/run_info.env.tmp"
fi
} > "$meta/run_info.env"
rm -f "$meta/run_info.env.tmp"
command -v pveversion >/dev/null 2>&1 && pveversion -v > "$meta/pveversion.txt" 2>&1 || true
command -v lsblk >/dev/null 2>&1 && lsblk -f > "$meta/lsblk.txt" 2>&1 || true
command -v qm >/dev/null 2>&1 && qm list > "$meta/qm-list.txt" 2>&1 || true

View File

@@ -3,7 +3,7 @@
### Nueva versión ProxMenux v1.2.4
Esta versión suma dos mejoras visibles en el propio dashboard — un botón para lanzar la actualización de Proxmox desde el Monitor de Salud, y un prompt para invitar a los usuarios móviles a instalar el Monitor como PWA — extiende el flujo de restauración de Backups con importación automática de pools ZFS de datos, refina el comportamiento de Log2RAM en hosts que corren Proxmox Backup Server como servicio, refuerza el ajuste de sysctl de los bridges del firewall en todo el ciclo de vida de VMs, ajusta la optimización de ZFS ARC a su propio ámbito, hace idempotentes los nombres persistentes de NIC en re-ejecuciones, recompila automáticamente los drivers DKMS cuando entra un kernel nuevo, mantiene intacta la sesión de la terminal del Monitor cuando hay update de ProxMenux disponible, y afina cinco plantillas de notificaciones y tres chequeos del panel de salud.
Esta versión suma dos mejoras visibles en el propio dashboard — un botón para lanzar la actualización de Proxmox desde el Monitor de Salud, y un prompt para invitar a los usuarios móviles a instalar el Monitor como PWA — extiende el flujo de restauración de Backups con snapshots atómicos de pmxcfs (`config.db`) e importación automática de pools ZFS de datos, refina el comportamiento de Log2RAM en hosts que corren Proxmox Backup Server como servicio, refuerza el ajuste de sysctl de los bridges del firewall en todo el ciclo de vida de VMs, ajusta la optimización de ZFS ARC a su propio ámbito, hace idempotentes los nombres persistentes de NIC en re-ejecuciones, recompila automáticamente los drivers DKMS cuando entra un kernel nuevo, mantiene intacta la sesión de la terminal del Monitor cuando hay update de ProxMenux disponible, y afina cinco plantillas de notificaciones y tres chequeos del panel de salud.
---
## 🩺 Botón Update Now en el Monitor de Salud
@@ -12,7 +12,7 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par
- Ejecuta el flujo estándar de actualización de Proxmox (`apt update` + `dist-upgrade` + limpieza posterior) en una terminal dentro del dashboard — sin necesidad de abrir una shell.
- Solo aparece cuando hay actualizaciones pendientes; si el sistema está al día, el botón queda oculto.
- Al cerrar, el Monitor de Salud fuerza un refresh con invalidación de caché (`/api/health/full?refresh=1`) para que el contador de actualizaciones pendientes y la fila del kernel reflejen el estado post-actualización al instante, en lugar del valor pre-actualización que la caché de fondo había guardado justo antes de la ejecución.
- El script subyacente distingue el contexto: si se ejecuta sobre un host ya en producción, respeta los repositorios personalizados del operador (no desactiva enterprise/ceph, no borra sources legacy, no purga NTP alternativos ni fuerza instalar zfsutils/chrony); si detecta un servidor virgen crea los repos base necesarios. Detecta también si hay un kernel nuevo instalado distinto del que está corriendo y pide el reboot al terminar.
- El script subyacente distingue el contexto: si se ejecuta sobre un host ya en producción, respeta los repositorios personalizados del usuario (no desactiva enterprise/ceph, no borra sources legacy, no purga NTP alternativos ni fuerza instalar zfsutils/chrony); si detecta un servidor virgen crea los repos base necesarios. Detecta también si hay un kernel nuevo instalado distinto del que está corriendo y pide el reboot al terminar.
- Durante la actualización se suprimen las notificaciones de `service_fail` de servicios PVE (pve-cluster, pveproxy, corosync…) porque su reinicio es parte normal del upgrade; volvían a notificarse hasta 60 s después de que apt termine.
---
@@ -56,7 +56,7 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par
- **La terminal WebSocket del Monitor expone ahora `PROXMENUX_TERMINAL=monitor`** en el entorno de cada shell que abre, y cada proceso hijo lo hereda. Esto le da a `menu` (y a cualquier otro flow que le importe) una vía fiable y determinista para saber que la sesión actual vive dentro del proceso del Monitor — una sesión que quedaría cortada a mitad de instalación si el servicio del Monitor se reiniciara.
- **Prompt de update de `menu`** — cuando hay una nueva versión de ProxMenux disponible y la sesión corre dentro de la terminal del Monitor, el clásico prompt yes/no se sustituye por un msgbox informativo. El msgbox nombra la nueva versión y muestra el comando canónico de una línea (`bash -c "$(wget -qLO - …)"`) para ejecutar el update desde SSH o la consola del host Proxmox. Como el flow ya ha decidido que el path in-terminal es inseguro, hay un solo botón OK — no hay yes/no que pudiera disparar el update destructivo por accidente.
- **Ajustes → Canal de release** — el mismo guard se aplica en `apply_release_channel()` de `config_menu.sh`. Seleccionar Stable ↔ Beta desde la terminal del Monitor muestra un msgbox informativo con el comando `wget` exacto para el canal destino (usando la misma URL que el flow habría descargado por su cuenta) y vuelve al menú en lugar de ejecutar el installer en el sitio.
- **Tras pulsar OK, ambos flows continúan con normalidad.** El operador sigue usando ProxMenux desde la misma terminal sin restricciones; solo el paso destructivo queda enrutado a otro sitio. No hay bloqueo ni acción forzada.
- **Tras pulsar OK, ambos flows continúan con normalidad.** El usuario sigue usando ProxMenux desde la misma terminal sin restricciones; solo el paso destructivo queda enrutado a otro sitio. No hay bloqueo ni acción forzada.
- **Las sesiones SSH, la consola del host Proxmox y cualquier entorno donde `PROXMENUX_TERMINAL` no sea `monitor` mantienen el comportamiento anterior** y pueden actualizar o cambiar de canal como siempre. El cambio solo afecta al caso en el que hacerlo en el sitio rompería la sesión activa.
- **Nota de bootstrap**: como `PROXMENUX_TERMINAL=monitor` lo añade el AppImage que ship-ea esta release, el guard solo empieza a proteger sesiones una vez que el host está en 1.2.4 o posterior. La primera actualización a 1.2.4, si se dispara desde la terminal del Monitor, aún puede caer en el comportamiento antiguo — a partir de 1.2.4 el guard queda en su sitio.
@@ -65,16 +65,16 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par
## 🔧 Flujo de actualización — Drivers DKMS recompilados cuando entra un kernel nuevo
- **Cuando `apt full-upgrade` deja preparado un kernel más nuevo que el que está en marcha, `update-pve-safe.sh` recompila ahora los drivers DKMS instalados por ProxMenux contra ese nuevo kernel.** El botón Update Now del Monitor de Salud y la utilidad CLI `utilities/proxmox_update.sh` delegan ambos en `update-pve-safe.sh`, así que las dos vías ganan el comportamiento. El paso lee `components_status.json`, cruza los componentes DKMS que ProxMenux gestiona (`nvidia_driver`, `coral_driver`), instala las cabeceras de kernel correspondientes (`proxmox-headers-<newkver>` o `pve-headers-<newkver>`) si no están ya presentes, y ejecuta `dkms autoinstall -k <newkver>`. Después verifica vía `dkms status` que cada módulo esperado (`gasket` para Coral, `nvidia` para el driver NVIDIA) haya alcanzado el estado `installed` para el nuevo kernel — si alguno no lo hizo, cae al camino `--auto-reinstall` de cada instalador.
- **Un whiptail msgbox anuncia la recompilación antes de ejecutarla.** Un solo botón OK — no hay yes/no. Nombra el kernel entrante y lista los componentes DKMS que se van a recompilar, de modo que el operador ve exactamente qué está a punto de suceder. Dado que dejar los drivers DKMS sin recompilar dejaría el sistema con un kernel funcional pero TPU / GPU no operativos al arrancar, se trata de transparencia, no de decisión — al pulsar OK se reconoce el trabajo posterior y el flujo procede. Las invocaciones no interactivas (cron, batch sin terminal, sin whiptail) omiten el msgbox y registran la misma información.
- **Un whiptail msgbox anuncia la recompilación antes de ejecutarla.** Un solo botón OK — no hay yes/no. Nombra el kernel entrante y lista los componentes DKMS que se van a recompilar, de modo que el usuario ve exactamente qué está a punto de suceder. Dado que dejar los drivers DKMS sin recompilar dejaría el sistema con un kernel funcional pero TPU / GPU no operativos al arrancar, se trata de transparencia, no de decisión — al pulsar OK se reconoce el trabajo posterior y el flujo procede. Las invocaciones no interactivas (cron, batch sin terminal, sin whiptail) omiten el msgbox y registran la misma información.
- **Solo se consideran componentes registrados como `installed` en `components_status.json`.** Un host sin drivers DKMS gestionados por ProxMenux no ve msgbox ni paso de recompilación. Los hosts que nunca ejecutaron el instalador de Coral o NVIDIA no se ven afectados.
- **Si la recompilación falla, no se aborta la actualización.** Si un módulo DKMS no puede recompilarse contra el nuevo kernel (ruptura de API upstream, dependencia faltante), el flujo de actualización termina normalmente, se nombran los componentes concretos que fallaron en el resumen, y el operador puede re-ejecutar su instalador a mano tras el reboot. El paso es best-effort por diseño — un desajuste kernel/driver es un problema upstream, no algo que el flujo de actualización deba tratar como fallo.
- **Si la recompilación falla, no se aborta la actualización.** Si un módulo DKMS no puede recompilarse contra el nuevo kernel (ruptura de API upstream, dependencia faltante), el flujo de actualización termina normalmente, se nombran los componentes concretos que fallaron en el resumen, y el usuario puede re-ejecutar su instalador a mano tras el reboot. El paso es best-effort por diseño — un desajuste kernel/driver es un problema upstream, no algo que el flujo de actualización deba tratar como fallo.
- El helper compartido `pmx_rebuild_dkms_after_kernel` vive en `scripts/global/utils-install-functions.sh`, de modo que futuros actualizadores o utilidades CLI pueden reutilizarlo con una única llamada.
---
## 🔌 Post-install — Nombres persistentes de NIC ahora idempotentes
- **Los ficheros `.link` gestionados por ProxMenux llevan ahora un prefijo distintivo y un marcador interno.** Se escriben como `10-proxmenux-<iface>.link` y la primera línea de cada fichero es `# Managed by ProxMenux — do not edit`. Ambos se comprueban en los pasos de reconciliación y desinstalación antes de tocar un fichero, de modo que cualquier cosa que el operador haya escrito a mano o que venga de otro paquete queda a salvo.
- **Los ficheros `.link` gestionados por ProxMenux llevan ahora un prefijo distintivo y un marcador interno.** Se escriben como `10-proxmenux-<iface>.link` y la primera línea de cada fichero es `# Managed by ProxMenux — do not edit`. Ambos se comprueban en los pasos de reconciliación y desinstalación antes de tocar un fichero, de modo que cualquier cosa que el usuario haya escrito a mano o que venga de otro paquete queda a salvo.
- **Cada re-ejecución de `setup_persistent_network` reconcilia las entradas de ProxMenux.** En cada invocación se recorren los ficheros `10-proxmenux-*.link` existentes, se extrae el valor `MACAddress=`, se compara con las MAC actualmente presentes en `/sys/class/net/`, y se eliminan únicamente las entradas propiedad de ProxMenux cuya MAC ya no está. Reemplazos de tarjetas, cambios de NIC y migraciones de hardware dejan de acumular mapeos huérfanos en cada re-ejecución.
- **Los ficheros con formato 1.0 (`10-<iface>.link`, escritos por la versión anterior) se migran automáticamente en la primera ejecución de la nueva función.** Si el fichero coincide con la plantilla exacta que usaba el código 1.0 (dos secciones, `MACAddress=` + `Name=`, nada más), se elimina y se reemplaza por el `10-proxmenux-<iface>.link` en un solo paso. Cualquier fichero que no coincida con esa plantilla exacta se deja intacto.
- **El desinstalador (`uninstall_persistent_network`) elimina ahora solamente ficheros que cumplan a la vez el prefijo `10-proxmenux-` y el marcador en la primera línea.** El anterior barrido `rm -f /etc/systemd/network/*.link` desaparece — los `.link` escritos por el usuario permanecen en su sitio con independencia de su nombre.
@@ -85,7 +85,7 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par
## 🧮 Post-install — Optimización de ZFS ARC ajustada a su ámbito
- **`optimize_zfs_arc` establece ahora únicamente `zfs_arc_max`.** La función escribe una sola línea en `/etc/modprobe.d/99-zfsarc.conf`: `options zfs zfs_arc_max=<cap>`. `zfs_arc_min` queda en el valor por defecto de OpenZFS (auto-calculado como el mayor entre 32 MiB y ~1/32 de la RAM), y los tunables de L2ARC (`l2arc_noprefetch`, `l2arc_write_max`) y de TXG (`zfs_txg_timeout`) — que quedan fuera del ámbito de una optimización del ARC — se dejan en los valores por defecto de OpenZFS salvo que el operador los configure explícitamente.
- **`optimize_zfs_arc` establece ahora únicamente `zfs_arc_max`.** La función escribe una sola línea en `/etc/modprobe.d/99-zfsarc.conf`: `options zfs zfs_arc_max=<cap>`. `zfs_arc_min` queda en el valor por defecto de OpenZFS (auto-calculado como el mayor entre 32 MiB y ~1/32 de la RAM), y los tunables de L2ARC (`l2arc_noprefetch`, `l2arc_write_max`) y de TXG (`zfs_txg_timeout`) — que quedan fuera del ámbito de una optimización del ARC — se dejan en los valores por defecto de OpenZFS salvo que el usuario los configure explícitamente.
- **Se regenera ahora el initramfs tras escribir el fichero.** En sistemas con ZFS-on-root el módulo ZFS se carga desde el initramfs antes de que el sistema en marcha lea `/etc/modprobe.d/`, así que un simple reboot no bastaba para que el nuevo cap surtiera efecto. `update-initramfs -u -k all` se ejecuta justo después de escribir el fichero, más `proxmox-boot-tool refresh` en hosts con systemd-boot, de modo que el valor se aplica en el siguiente arranque en lugar de quedar sombreado por la copia obsoleta del initramfs.
- **La función se protege con la presencia de un pool ZFS vivo** (chequeo `zpool list`), de manera que se convierte en no-op en hosts que no usan ZFS.
- **Los valores del cap usan tamaños binarios limpios**: 512 MiB hasta 16 GB de RAM, 1 GiB hasta 32 GB, RAM/8 por encima — con un suelo de 512 MiB para que una lectura defectuosa de memoria no deje un ARC inutilizablemente pequeño.
@@ -108,7 +108,9 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par
- **Script upstream `log2ram` parcheado a `rsync -aXv --no-acls` justo después de `install.sh`.** Ambos instaladores reescriben la llamada en el sitio con un `sed` guardado por `grep -q` (deja backup en `.proxmenux.bak`, no-op si una futura release upstream ya no incluye `-A`). Los atributos extendidos (`-X`) se preservan. Resultado: `log2ram write` finaliza limpio en sistemas de ficheros de `/var/log.hdd` que no aceptan ACLs POSIX (ZFS con `acltype=off`, ext4 montado sin la opción `acl`) — sin mensajes de `set_acl: Operation not supported` / salida 23.
- **El bloque de emergencia de `log2ram-check.sh` rota los logs de PBS antes de truncar.** Cuando `/var/log` cruza el umbral del 92%, el script de auto-sync ejecuta ahora `logrotate -f /etc/logrotate.d/proxmox-backup-api` (solo si el fichero de regla existe) *antes* de truncar `pveproxy/access.log`, `pveproxy/error.log` y `pveam.log`. El historial reciente de accesos/autenticación de PBS se conserva en los `.gz` rotados en lugar de perderse. Se subió `FUNC_VERSION` de 1.2 → 1.3 en `install_log2ram_auto` y `configure_log2ram`, y el comentario de cabecera del `log2ram-check.sh` embebido de v1.2 → v1.3.
## 🗄 Restauración de backup — importación automática de pools ZFS de datos
## 🗄 Restauración de backup — snapshot de pmxcfs + pools ZFS de datos
- **`/var/lib/pve-cluster/config.db` se captura ahora con `sqlite3 .backup`.** pmxcfs (`/etc/pve`) es servido por `pve-cluster` desde ese almacén SQLite, así que un rsync directo del fichero raw con el servicio en marcha puede pillarlo a mitad de checkpoint WAL y aterrizar en el archivo como una copia inconsistente. `hb_prepare_staging` ejecuta ahora `sqlite3 /var/lib/pve-cluster/config.db ".backup '$staging/…/config.db'"` antes del rsync general — la vía canónica (documentada por Proxmox) para snapshotear el almacén de forma consistente mientras `pve-cluster` sigue sirviendo tráfico, con downtime cero para el cluster. El rsync general de `/var/lib/pve-cluster` excluye ahora `config.db`, `config.db-wal` y `config.db-shm` para que nada sobrescriba el dump atómico. Los hosts sin `sqlite3` recurren a una copia raw llamada `config.db.raw-fallback`, que el helper de recuperación promociona a `config.db` antes de arrancar `pve-cluster`. Los metadatos registran qué vía se usó vía `pmxcfs_config_db=sqlite_backup|raw_fallback` en `metadata/run_info.env` para trazabilidad. La ruta de restauración sigue usando el patrón canónico `systemctl stop pve-cluster → cp → systemctl start pve-cluster` (`apply_pending_restore.sh` y el helper de recuperación standalone que se escribe junto a cada directorio de cluster extraído), así que la DB que el usuario trae de vuelta es ahora consistente garantizada en lugar de una copia raw de estado en vuelo.
- **Los pools ZFS de datos listados en el backup ahora se importan automáticamente durante la restauración.** El nuevo paso `_rs_import_data_pools` corre tras el apply de configs, recorre `storage_inventory.zfs_pools[]`, excluye el pool raíz (ya montado por el sistema) y lanza `zpool import <nombre>` para cada pool no-raíz cuyos discos estén todos presentes en este host. Cuando ZFS rechaza el import por *foreign* — el caso típico tras una instalación fresh que regraba la etiqueta on-disk con un `hostid` nuevo — el paso reintenta con `-f` y reporta el pool como forzado para dejar trazabilidad. Los pools a los que les falta algún disco se omiten con un aviso claro en lugar de importarse en modo degradado. Todo esto cierra el caso habitual en el que `zfs-import-scan.service` fallaba al boot tras una instalación fresh y dejaba el pool de datos separado indisponible hasta ejecutar `zpool import -f` a mano.
- **El resultado del auto-import persiste en la tarjeta post-restauración.** El paso escribe una sección `data_pools_import` en `/var/lib/proxmenux/restore-state.json` (el mismo JSON que la tarjeta de la pestaña Backups consulta) y un log crudo en `/var/log/proxmenux/restore-datapools-<timestamp>.log`. La tarjeta muestra dentro de Detalles un bloque dedicado con cinco filas coloreadas (Importados / Forzados / Omitidos parcial / Omitidos ausentes / Fallidos), así que el resumen queda consultable después de cerrar el terminal de restauración, y la entrada se preserva en el historial del run para consulta posterior.
@@ -234,13 +236,13 @@ Y a cada usuario que abrió una issue, comentó en [GitHub Discussions](https://
### Nueva versión ProxMenux v1.2.2 — *Consolidación estable del ciclo v1.2.1.x*
Release estable que lleva al canal principal las cuatro prereleases del ciclo **v1.2.1.x** en un solo movimiento. El trabajo a lo largo de esas cuatro betas se centró en tres temas: hacer del Health Monitor algo realmente configurable en lugar de solo observable (thresholds por categoría, duraciones de dismiss por evento, un audit log de supresiones activas), expandir el stack de notificaciones para cubrir alrededor de 80 servicios a través de Apprise mientras se persisten eventos durante las Quiet Hours, y convertir el propio proceso del Monitor en un ciudadano del sistema más silencioso y predecible en hosts idle. Por encima de eso, esta release entrega detección automática de updates en contenedores LXC, una reescritura end-to-end del instalador de Coral TPU con los últimos drivers upstream, y una larga lista de fixes visibles para el operador — handshake del terminal HTTPS, detección de kernel updates en PVE 9.x, flujo del instalador NVIDIA en Alpine LXC, gestión del audio acompañante en passthrough de GPU mixta, y varias optimizaciones runtime en los bucles de scan del Monitor. Cinco contribuciones de código directas de la comunidad shipean junto con esta release ([@jcastro](https://github.com/jcastro) ×5, [@pespinel](https://github.com/pespinel) ×1) y el trabajo de GPU passthrough lo impulsaron los reports detallados de campo de [@ghosthvj](https://github.com/ghosthvj) — ver los Acknowledgments al final.
Release estable que lleva al canal principal las cuatro prereleases del ciclo **v1.2.1.x** en un solo movimiento. El trabajo a lo largo de esas cuatro betas se centró en tres temas: hacer del Health Monitor algo realmente configurable en lugar de solo observable (thresholds por categoría, duraciones de dismiss por evento, un audit log de supresiones activas), expandir el stack de notificaciones para cubrir alrededor de 80 servicios a través de Apprise mientras se persisten eventos durante las Quiet Hours, y convertir el propio proceso del Monitor en un ciudadano del sistema más silencioso y predecible en hosts idle. Por encima de eso, esta release entrega detección automática de updates en contenedores LXC, una reescritura end-to-end del instalador de Coral TPU con los últimos drivers upstream, y una larga lista de fixes visibles para el usuario — handshake del terminal HTTPS, detección de kernel updates en PVE 9.x, flujo del instalador NVIDIA en Alpine LXC, gestión del audio acompañante en passthrough de GPU mixta, y varias optimizaciones runtime en los bucles de scan del Monitor. Cinco contribuciones de código directas de la comunidad shipean junto con esta release ([@jcastro](https://github.com/jcastro) ×5, [@pespinel](https://github.com/pespinel) ×1) y el trabajo de GPU passthrough lo impulsaron los reports detallados de campo de [@ghosthvj](https://github.com/ghosthvj) — ver los Acknowledgments al final.
---
## 🩺 Health Monitor — Configurable, granular, auditable
Tres piezas acopladas que juntas permiten al operador ajustar el Health Monitor a la envoltura real de su host en lugar de trabajar alrededor de sus defaults, y gestionar dismisses con el mismo control fino que ya tienen sobre el resto del dashboard.
Tres piezas acopladas que juntas permiten al usuario ajustar el Health Monitor a la envoltura real de su host en lugar de trabajar alrededor de sus defaults, y gestionar dismisses con el mismo control fino que ya tienen sobre el resto del dashboard.
### Thresholds Warning / Critical por categoría
@@ -260,7 +262,7 @@ El botón *Dismiss* en cada alerta del Health Monitor abre ahora un pequeño dro
- **7 days** — útil para una condición temporal de la que no quieres oír durante una migración de una semana
- **Permanently** — silencia este `error_key` concreto indefinidamente
Los dismisses permanentes se persisten con `suppression_hours = -1` en la base de datos de persistencia, nunca re-emiten, nunca re-notifican y se marcan con un badge ámbar **Permanent** distinto en el Health Monitor para que el operador siempre sepa qué alertas están silenciadas intencionadamente. La infraestructura backend para el centinela permanente ya existía — solo le faltaba a la UI una forma de fijarlo. El contrato de API es pequeño y backwards-compatible: `POST /api/health/acknowledge` acepta un campo body opcional `suppression_hours` (entero positivo para horas, `-1` para permanente); omitirlo preserva el comportamiento previo y usa la supresión configurada de la categoría. Un segundo endpoint nuevo `POST /api/health/un-acknowledge {error_key}` limpia un acknowledgment previamente registrado para que la alerta vuelva a ser elegible para dispararse — usado por el panel Active Suppressions abajo.
Los dismisses permanentes se persisten con `suppression_hours = -1` en la base de datos de persistencia, nunca re-emiten, nunca re-notifican y se marcan con un badge ámbar **Permanent** distinto en el Health Monitor para que el usuario siempre sepa qué alertas están silenciadas intencionadamente. La infraestructura backend para el centinela permanente ya existía — solo le faltaba a la UI una forma de fijarlo. El contrato de API es pequeño y backwards-compatible: `POST /api/health/acknowledge` acepta un campo body opcional `suppression_hours` (entero positivo para horas, `-1` para permanente); omitirlo preserva el comportamiento previo y usa la supresión configurada de la categoría. Un segundo endpoint nuevo `POST /api/health/un-acknowledge {error_key}` limpia un acknowledgment previamente registrado para que la alerta vuelva a ser elegible para dispararse — usado por el panel Active Suppressions abajo.
### Panel Active Suppressions en Settings
@@ -288,7 +290,7 @@ Tres fixes de fiabilidad shipean junto, todos surfaceados después del rollout b
2. **Regresión del whitelist backend** que rechazaba Apprise con HTTP 400. El conjunto de canales hardcodeado del validador de notifications-test (`{telegram, gotify, discord, email, all}`) tenía a `apprise` ausente, por lo que cada test o send de Apprise devolvía `400 Invalid channel` antes de que la librería fuera siquiera invocada. El whitelist se deriva ahora en vivo desde `notification_channels.CHANNEL_TYPES`, de forma que añadir una nueva implementación de canal en el futuro no puede regresionar silenciosamente este validador otra vez.
3. **Error reporting opaco** cuando el destino devolvía una respuesta no-2xx. Cuando un destino (`jsons://`, `ntfy://`, `slack://`, …) rechazaba el payload, el operador solo veía un mensaje genérico *"Apprise rejected the notification (transport failure)"*. El canal captura ahora el logger interno de Apprise durante `notify()` y surfacea el HTTP status code real más el response body del destino (capado a 300 caracteres) — de forma que un beta tester debuggeando un webhook custom puede ver inmediatamente si el servidor upstream está rechazando su schema de payload.
3. **Error reporting opaco** cuando el destino devolvía una respuesta no-2xx. Cuando un destino (`jsons://`, `ntfy://`, `slack://`, …) rechazaba el payload, el usuario solo veía un mensaje genérico *"Apprise rejected the notification (transport failure)"*. El canal captura ahora el logger interno de Apprise durante `notify()` y surfacea el HTTP status code real más el response body del destino (capado a 300 caracteres) — de forma que un beta tester debuggeando un webhook custom puede ver inmediatamente si el servidor upstream está rechazando su schema de payload.
---
@@ -325,7 +327,7 @@ El mount monitor solía llamar `lxc-info -n <vmid> -p` por cada CT corriendo sol
## 🔌 Handshake del terminal HTTPS
Cada modal de terminal en el Monitor (terminal del dashboard, terminal LXC, terminal de scripts) solía fallar con *WebSocket connection error* en hosts donde HTTPS estaba habilitado. La root cause era específica al path `gevent + SSL`: el `WebSocketHandler` de gevent-websocket estaba apilado sobre la implementación de protocolo de flask-sock, por lo que el servidor emitía **dos** cabeceras `HTTP/1.1 101 Switching Protocols` consecutivas y el navegador cerraba la conexión como un frame corrupto. Quitar el argumento explícito `handler_class=WebSocketHandler` restaura una única respuesta 101 y el handshake completa con normalidad. El fix es invisible para operadores corriendo en HTTP plano — no estaban afectados — pero desbloquea cada install fronteada por HTTPS (reverse proxies, deployments con certificate-managed, cualquier cosa detrás de nginx/Traefik).
Cada modal de terminal en el Monitor (terminal del dashboard, terminal LXC, terminal de scripts) solía fallar con *WebSocket connection error* en hosts donde HTTPS estaba habilitado. La root cause era específica al path `gevent + SSL`: el `WebSocketHandler` de gevent-websocket estaba apilado sobre la implementación de protocolo de flask-sock, por lo que el servidor emitía **dos** cabeceras `HTTP/1.1 101 Switching Protocols` consecutivas y el navegador cerraba la conexión como un frame corrupto. Quitar el argumento explícito `handler_class=WebSocketHandler` restaura una única respuesta 101 y el handshake completa con normalidad. El fix es invisible para usuarios corriendo en HTTP plano — no estaban afectados — pero desbloquea cada install fronteada por HTTPS (reverse proxies, deployments con certificate-managed, cualquier cosa detrás de nginx/Traefik).
Adicionalmente, el panel de terminal solía perder su conexión WebSocket cuando el usuario activaba la feature de auto-traducción del navegador (los prompts "translate this page" de Chrome / Edge / Safari). El traductor mueve nodos del DOM que React aún mantiene como refs, y el componente WebSocket React se rompe porque su ref de contenedor apunta a un nodo movido. Añadido `translate="no"` en los divs contenedores del terminal para que el traductor salte el tty embebido por completo — las traducciones en el resto de la página siguen funcionando.
@@ -339,7 +341,7 @@ En hosts Proxmox VE 9.x, la fila *System Updates → Kernel / PVE* reportaba "Ke
2. **El dry-run cambió de `apt-get upgrade --dry-run` a `apt-get dist-upgrade --dry-run`**. PVE 9 shipea kernel updates empaquetados como instalaciones nuevas (no como upgrades directas de un paquete existente), y el `upgrade --dry-run` plano no considera nuevas instalaciones en absoluto. `dist-upgrade --dry-run` sí.
3. **La detección del kernel corriendo** lee ahora `uname -r` y flaguea un update como *running-kernel update* cuando el paquete matchea la release corriendo exactamente o su meta-package de branch (p. ej. `proxmox-kernel-6.14` para un host en `6.14.11-4-pve`). El texto de la fila distingue *"Running kernel update available (reboot required)"* de *"N kernel update(s) available (none for running kernel)"* para que el operador sepa si necesita reboot o solo instalar.
3. **La detección del kernel corriendo** lee ahora `uname -r` y flaguea un update como *running-kernel update* cuando el paquete matchea la release corriendo exactamente o su meta-package de branch (p. ej. `proxmox-kernel-6.14` para un host en `6.14.11-4-pve`). El texto de la fila distingue *"Running kernel update available (reboot required)"* de *"N kernel update(s) available (none for running kernel)"* para que el usuario sepa si necesita reboot o solo instalar.
---
@@ -369,9 +371,9 @@ Nuevas páginas de documentación cubren la sección **Active Suppressions** en
- **Detección de updates de funciones post-install** — el Monitor trackea optimizaciones ProxMenux instaladas (Log2Ram, Memory Settings, System Limits, Logrotate, …) y notifica cuando hay una versión más nueva disponible, con apply one-click desde Settings.
- **Flujo de update de Secure Gateway (Tailscale)** — update one-click de Tailscale desde Settings con indicadores Last-checked / Installed / Latest y notificación cuando se publica una nueva versión.
- **Menú Helper-Scripts** — context más rico e información útil para cada entrada, haciendo más fácil saber qué hace cada script antes de ejecutarlo.
- **Wording de agregación burst** — los resúmenes burst reportan ahora solo los eventos *adicionales* que llegaron después de la alerta individual inicial, de forma que el operador ya no ve el primer evento contado dos veces.
- **Wording de agregación burst** — los resúmenes burst reportan ahora solo los eventos *adicionales* que llegaron después de la alerta individual inicial, de forma que el usuario ya no ve el primer evento contado dos veces.
- **Clasificador de errores conocidos** — regex con word-boundary en patrones ATA / UNC para que mensajes de kernel como `nvidia_uvm:FatalError` ya no se clasifiquen mal como problemas de cable ATA.
- **Errores de control de VM / CT** — start / stop / restart fallido surfacea ahora el stderr real de `pvesh` (p. ej. *"no space left on device"*) en el toast de la UI y dispara una notificación `vm_fail` / `ct_fail`, en lugar del bare 500 INTERNAL SERVER ERROR que el operador solía ver.
- **Errores de control de VM / CT** — start / stop / restart fallido surfacea ahora el stderr real de `pvesh` (p. ej. *"no space left on device"*) en el toast de la UI y dispara una notificación `vm_fail` / `ct_fail`, en lugar del bare 500 INTERNAL SERVER ERROR que el usuario solía ver.
- **Path de apply de log2ram** — el flujo auto / update reinicia ahora log2ram después de escribir el nuevo size, de forma que un `512M` configurado realmente surte efecto en el tmpfs corriendo sin restart manual.
- **PVE webhook URL** — el webhook de notificación sigue ahora automáticamente el estado SSL activo, cambiando entre `http://` y `https://` cuando toggleas HTTPS en el panel.
- **Cascada de 401 frontend** — la login screen ya no se traga un 401 para siempre tras un estado breve de token rancio; la flag de dedup se limpia al mount y al login exitoso.