Release 1.2.2 — version.txt bump + CHANGELOG (EN+ES) + contributors

Final ingredient of the v1.2.2 stable release: flip version.txt from
1.2.1 to 1.2.2 so the stable channel's update notifier picks it up
on every running install, ship the consolidated v1.2.2 entry on both
CHANGELOG.md (English) and lang/es/CHANGELOG.md (Spanish), and add
the GitHub link to Jonatan Castro on the contributors page.

CHANGELOG.md entry (and its ES mirror) consolidates the four v1.2.1.x
betas into a single stable note grouped by theme — Health Monitor
configurability, Apprise full feature parity, LXC update detection,
Coral TPU latest upstream drivers, performance optimizations (smartctl
scheduler, fail2ban cache, lxc-info /proc), HTTPS terminal handshake,
PVE 9.x kernel update detection, NVIDIA installer improvements, i18n
documentation site — plus an Acknowledgments section crediting
@jcastro (5 direct commits), @pespinel (1 commit) and @ghosthvj
(field reports that shaped the GPU + Coral work).

contributors/page.tsx: Contributor interface now carries an optional
`githubUrl`; when set, the displayed name is wrapped in an
ExternalLink to that URL (target=_blank). Jonatan Castro's entry gets
`githubUrl: https://github.com/jcastro` so users can reach his repos
from the testers grid.

After this PR merges:
- Users running `menu` will be offered the 1.2.2 upgrade
- proxmenux.com/en/changelog and /es/changelog ship the new entry
  (deploy.yml triggers because CHANGELOG.md, lang/** and web/** are
  all touched)
- Jonatan Castro's name on the contributors page becomes clickable

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-05-31 19:14:34 +02:00
parent d022c4fe81
commit e83950c0c2
4 changed files with 371 additions and 4 deletions
+177 -2
View File
@@ -1,9 +1,184 @@
## 2026-05-31
### 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.
---
## 🩺 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.
### Per-category Warning / Critical thresholds
Every check the Health Monitor runs is parameterised by a pair of numbers — a *Warning* and a *Critical* — and both are now exposed under **Settings → Health Monitor Thresholds**. The defaults that ship with ProxMenux are reasonable for the average Proxmox host, but every environment has its own envelope:
- A small homelab with a single-disk SSD wants to page earlier on capacity (75 / 90 %) to leave room for snapshots.
- A datacentre node with redundant Ceph storage can be far more relaxed on memory warnings (90 % working set is normal under ZFS ARC).
- A passively-cooled mini-PC needs lower temperature thresholds than a server with forced-air cooling — same drive class, different physical envelope.
Edits take effect on the next scan — the Health Monitor re-reads the values from `/usr/local/share/proxmenux/health_thresholds.json` on every cycle, no service restart. The same numbers also feed the colour ranges of every dashboard widget (storage bars, CPU/memory rings, temperature chips, the dot on the disk modal), so the visual classification anywhere in the Monitor maps to a definite range relative to the configured pair.
### Per-event dismiss duration with a Permanent badge
The *Dismiss* button on each Health Monitor alert now opens a small dropdown with three options:
- **24 hours** — previous default, behaves exactly as before
- **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.
### Active Suppressions panel in Settings
A new section inside **Settings → Health Monitor**, right below the per-category suppression durations, lists every currently-silenced alert — both time-limited dismisses (with a *22h remaining* / *6d remaining* badge) and permanent ones (with the amber *Permanent* badge from the dashboard). Each row carries the `error_key`, category, severity, the timestamp the dismiss was recorded, and a **Re-enable** button that clears the acknowledgment server-side. Re-enables are **queued** — clicking the button marks the row green with a strike-through and changes the button to *Undo*, and the actual `POST /api/health/un-acknowledge` only fires when the user clicks **Save**, so a batch of re-enables ships atomically alongside any pending per-category dropdown changes. The action is gated by the Health Monitor *Edit* toggle at the top of the card. Permanent dismisses **can only be reverted from here** — the dashboard intentionally does not expose a per-alert un-dismiss affordance to avoid accidental re-enables, so the Settings panel is the deliberate audit + revert surface for them. The list also refreshes automatically when an alert is dismissed from the Health Monitor modal while the Settings page is already open, via a `health-suppression-changed` browser event plus listeners on `window focus` and `document visibilitychange`.
### Disk I/O severity tiers
A sliding 24 h window now classifies dmesg ATA / SCSI errors into three buckets: silent (010 transient events), WARNING (11100) and CRITICAL (100+, or any hard error like UNC / Buffer I/O / Sense Key Hardware Error). Quiet days stay quiet, but a single Buffer I/O event still pages immediately.
---
## 📨 Apprise Notification Channel — Full Feature Parity
The Apprise integration that landed as a basic adapter in 1.2.1.4-beta has graduated to full parity with the native channels. One Apprise URL now reaches roughly 80 notification services (Pushover, ntfy, Slack, Matrix, mailto, signal, Pushbullet, Mattermost, Microsoft Teams via webhooks, …) without ProxMenux needing a dedicated adapter for each. The Apprise tab in Notifications exposes the same controls as Telegram, Gotify, Discord and Email:
- The full **Notification Categories** block — the same 10 categories with their per-event sub-toggles, identical to the other channels
- **Quiet Hours** — start/end window per channel, with the same buffering behaviour (events fired during the window are persisted to SQLite and released as a grouped summary when the window closes, rather than being silently dropped)
- **Daily Digest** — opt-in once-a-day summary delivery at a chosen time
The backend's per-channel filtering already applied generically to every channel including Apprise via the `channel_overrides` block — the UI just wasn't surfacing it.
Three reliability fixes ship alongside, all surfaced after the initial beta rollout:
1. **Mobile overflow** on narrow viewports. The Apprise URL row used to break the design — the placeholder packed four full example URLs into one line and the inline `<code>` examples had no `break-all` rule. The placeholder is now a single concise example (`tgram://bottoken/ChatID`), the URL input wrapper enforces `min-w-0 / flex-1 / shrink-0` on its children, and the examples paragraph uses `break-all min-w-0` so it wraps cleanly at any width.
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.
---
## 📦 LXC Update Detection
A new dedicated section in **Settings** (between *Health Monitor Thresholds* and *Notifications*) with a single toggle that gates the per-CT `apt list --upgradable` / `apk list -u` scan end-to-end. Default ON. When OFF the scan stops entirely (no `pct exec` calls), every `type=lxc` entry is purged from the managed-installs registry immediately, and the matching notification toggle in *Notifications → Services* disappears from the UI while preserving its stored preference.
The checker also reads the `mtime` of each CT's package-manager metadata cache and triggers `apt-get update` / `apk update` from outside via `pct exec` if it's older than 24 h, with a 60 s timeout and silent failure. Long-running appliance CTs whose caches were months stale finally surface their real upstream backlog — a Debian 12 CT with a 524-day-old cache went from "0 updates" to "117 (12 security)" on lab hardware.
---
## 🐧 Coral TPU on LXC — Latest Upstream Drivers
The Coral installer for LXC (`scripts/gpu_tpu/install_coral_lxc.sh`) was rewritten end-to-end to install the **latest upstream `gasket-dkms` driver** and the **latest `libedgetpu1` runtime** (220 lines added, 150 removed). Coral M.2 / mPCIe modules that previously failed to build on PVE 9 kernels now install and bind cleanly. The registry-driven update notifications that landed in 1.2.1.2 keep both packages fresh going forward: the Hardware tab + Notifications signal when feranick/gasket-driver publishes a newer release, and the `apt`-tracked `libedgetpu1` runtime gets the standard System Updates flow.
The companion **Coral installer uninstall path** also lands in this cycle — mirroring the NVIDIA flow so a Coral install can be cleanly reversed if the user re-deploys the host without TPU acceleration.
---
## ⚡ Monitor Performance Optimizations
A 10-minute strace + sampling run on a live host surfaced three places where the Monitor's background scanner was spawning subprocesses more aggressively than it needed to. All three are fixed in 1.2.2:
### fail2ban subprocess storm
On hosts where `fail2ban-client` was not installed, the cache wrapper around `_f2b_get_banned_ips()` only updated its timestamp on success. Every HTTP request to the dashboard fell through the cache check and fired a fresh `execve("fail2ban-client", ...)` that immediately failed with `ENOENT` — 250+ failed `execve` calls in a 10-minute window. `shutil.which('fail2ban-client')` is now resolved **once** at module load and the cache timestamp is updated unconditionally. Hosts without Fail2Ban now have zero `fail2ban-client` syscalls per request.
### smartctl scheduler collision
Disk SMART temperature polling, CPU temperature read and latency probe used to fire at the same offset within each minute, producing a measurable CPU / IO spike when all their subprocesses spawned together. The polls are now staggered (latency first, then CPU temperature, then disk SMART) while preserving the per-disk 60 s cadence — the spike is gone, total CPU under load is unchanged.
### LXC inventory subprocess
The mount monitor used to call `lxc-info -n <vmid> -p` for every running CT just to get its init PID. It now reads `/proc/<lxc-start-pid>/task/<lxc-start-pid>/children` directly and falls back to `lxc-info` only when the `/proc` read fails. One subprocess per CT per scan cycle eliminated — measurable on hosts with 20+ containers.
---
## 🔌 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).
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.
---
## 🐧 Health Monitor — Kernel Update Detection on PVE 9.x (#208)
On Proxmox VE 9.x hosts, the *System Updates → Kernel / PVE* row used to report "Kernel/PVE up to date" even when an update for the running kernel was waiting upstream. Three combined causes, three combined fixes:
1. **Kernel-package prefix list** now includes `proxmox-kernel-*` and `proxmox-firmware-*` — PVE 9.x ships kernels under `proxmox-kernel-`, not the `pve-kernel-` prefix from 7.x / 8.x. The previous regex never matched the new packages and so never flagged any kernel update on 9.x.
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.
---
## 🟢 NVIDIA Installer
Several improvements driven by [@ghosthvj](https://github.com/ghosthvj)'s detailed field reports on mixed GPU configurations (see Acknowledgments):
- **Kernel compatibility window** — the version menu now respects the running kernel's compatible driver range, only offering branches that won't fail to compile against the host kernel.
- **Alpine LXC support** — the container-side userspace install was reworked so it succeeds on Alpine hosts; free-space detection works reliably across all storage layouts (LVM-thin, ZFS, directory, etc).
- **NVENC patch awareness** — when the host has the NVENC patch applied, the version menu narrows to drivers supported by the patch so reinstalling never silently loses it.
- **Uninstall feedback** — the uninstall path now reports a clear completion message instead of returning to the menu silently.
---
## 🌐 Documentation Site — Full i18n Migration
The companion documentation site (proxmenux.com) now ships under locale-prefixed URLs (`/en/...` and `/es/...`) with the next-intl plumbing. Every doc page is bilingual — 107 pages translated to Spanish (no copy-of-English placeholders). The root `/` redirects to `/en/` via meta-refresh + JS so the apex URL still resolves to something useful. RSS feeds work per-locale at `/en/rss.xml` and `/es/rss.xml`, with the canonical `/rss.xml` kept for backward compatibility with existing feed subscribers. Client-side search is wired up via **Pagefind** — the index is built fresh on every CI deploy from the final HTML output and downloaded fragmentally by the client, so search works without a server backend.
New documentation pages cover the **Active Suppressions** section in the Settings tab and the **per-event Dismiss dropdown** in the Health Monitor modal, both with screenshots reflecting the new UI.
---
## 🔧 Other Improvements
- **AI Enhancement section in Notifications** — rewritten from a muted uppercase row that testers consistently scrolled past, to a normal-case foreground label with a leading `Sparkles` icon and a persistent badge (green *Active* when AI is enabled, neutral *Optional* when it isn't) so the feature is discoverable regardless of state.
- **Disk temperature monitoring** — improved readings, smarter caching across SMART probes, and a redesigned history modal that opens at 24 h by default with min / avg / max statistics.
- **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.
- **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.
- **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.
---
## 🙏 Acknowledgments
This release includes direct code contributions from the community and a substantial amount of design-shaping feedback. Particular thanks to:
### Code contributors
**[@jcastro](https://github.com/jcastro)** landed five direct improvements that ship with v1.2.2:
- **Select VM ISOs from all ISO storages** — new shared helper `scripts/global/iso_storage_helpers.sh` plus integration in `vm_creator.sh`, `select_linux_iso.sh` and `select_windows_iso.sh`. The ISO picker now reads from every Proxmox storage tagged as ISO content instead of being pinned to `local`. Commit [`092b548d`](https://github.com/MacRimi/ProxMenux/commit/092b548d).
- **Release channel switcher in Settings** — a proper menu under `scripts/menus/config_menu.sh` to flip between the stable and beta install channels in-place, with the right `version.txt` / `beta_version.txt` handling on each side. Commit [`f8a8c43d`](https://github.com/MacRimi/ProxMenux/commit/f8a8c43d).
- **ZFS autotrim in the auto post-install** — `auto_post_install.sh` now enables `autotrim=on` on root ZFS pools by default (with the matching disable in the uninstall path), so SSD-backed installs reclaim freed space without manual intervention. Commit [`8877f987`](https://github.com/MacRimi/ProxMenux/commit/8877f987).
- **Webhook loopback detection + update handoff** — `flask_notification_routes.py` correctly classifies `127.0.0.1` / `localhost` webhooks as loopback, and the `menu` script's update handoff no longer flakes on edge cases. Commit [`70ab072c`](https://github.com/MacRimi/ProxMenux/commit/70ab072c).
- **Figurine bumped to 2.0.0** — banner tool refresh in `customizable_post_install.sh`, with the doc page updated to match. Commit [`aba94028`](https://github.com/MacRimi/ProxMenux/commit/aba94028).
**[@pespinel](https://github.com/pespinel)** fixed a beta-installer regression that broke service paths after the move to the new runtime layout — `install_proxmenux_beta.sh` now resolves the right systemd unit paths on first install and on update. Commit [`0daab74a`](https://github.com/MacRimi/ProxMenux/commit/0daab74a).
### Field reports that shaped the GPU + Coral work
**[@ghosthvj](https://github.com/ghosthvj)**'s detailed reports and suggestions on the hardware passthrough flow drove the GPU script improvements in this release. The NVIDIA installer fixes, the GPU + audio companion lifecycle hardening on `switch_gpu_mode.sh`, and the iGPU audio-companion checklist on `add_gpu_vm.sh::detect_optional_gpu_audio` all started from his reports of edge cases that the previous code paths handled poorly.
### Everyone else
A huge thank you to every user who opened an issue on GitHub, commented in [GitHub Discussions](https://github.com/MacRimi/ProxMenux/discussions), reported a bug on the community channel, or stopped by to share what worked and what didn't on their hardware. **Many of the internal improvements in this release — the smartctl scheduler stagger, the fail2ban cache fix, the `lxc-info /proc` replacement, the HTTPS terminal handshake, the kernel-update detection on PVE 9.x, the entire Apprise wiring — started as a report from somebody running into the issue.** Keep them coming.
---
## 2026-04-20
### New version ProxMenux v1.2.1 — *SR-IOV Awareness & GPU Passthrough Hardening*
Targeted release on top of **v1.2.0** addressing three community-reported areas: complete SR-IOV awareness across the GPU/PCI subsystem, robust handling of GPU + audio companions during passthrough attach and detach (Intel iGPU with chipset audio, discrete cards with HDMI audio, mixed-GPU VMs), and compatibility fixes for AI notification providers (OpenAI-compatible custom endpoints such as LiteLLM/MLX/LM Studio, OpenAI reasoning models, and Gemini 2.5+/3.x thinking models). Also includes quality-of-life improvements in the NVIDIA installer, the disk health monitor, and the LXC lifecycle helpers used by the passthrough wizards.
Targeted release on top of **v1.2.0** addressing three community-reported areas that needed fixing before the next stable cycle: full SR-IOV awareness across the GPU/PCI subsystem, robust handling of GPU + audio companions during passthrough attach and detach (Intel iGPU with chipset audio, discrete cards with HDMI audio, mixed-GPU VMs), and compatibility fixes for the AI notification providers (OpenAI-compatible custom endpoints such as LiteLLM/MLX/LM Studio, OpenAI reasoning models, and Gemini 2.5+/3.x thinking models). Also bundles quality-of-life fixes in the NVIDIA installer, the disk health monitor, and the LXC lifecycle helpers used by the passthrough wizards.
---
@@ -1062,4 +1237,4 @@ Disks now display tags like ⚠ In use, ⚠ RAID, ⚠ LVM, or ⚠ ZFS, making it
## [1.0.0] - 2024-12-18
### Added
- Initial release of **ProxMenux**.
- Created a script to add **Coral TPU drivers** to Proxmox.
- Created a script to add **Coral TPU drivers** to Proxmox.
+175
View File
@@ -1,4 +1,179 @@
## 2026-05-31
### 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.
---
## 🩺 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.
### Thresholds Warning / Critical por categoría
Cada check que corre el Health Monitor está parametrizado por un par de números — un *Warning* y un *Critical* — y ambos están ahora expuestos bajo **Settings → Health Monitor Thresholds**. Los defaults que shipean con ProxMenux son razonables para el host Proxmox medio, pero cada entorno tiene su propia envoltura:
- Un homelab pequeño con un único SSD quiere paginar antes en capacidad (75 / 90 %) para dejar margen a snapshots.
- Un nodo de datacenter con almacenamiento Ceph redundante puede ser mucho más relajado con los warnings de memoria (un working set del 90 % es normal con ZFS ARC).
- Un mini-PC refrigerado pasivamente necesita thresholds de temperatura más bajos que un servidor con refrigeración forzada — misma clase de disco, distinta envoltura física.
Los cambios surten efecto en el siguiente scan — el Health Monitor relee los valores desde `/usr/local/share/proxmenux/health_thresholds.json` en cada ciclo, sin restart del servicio. Los mismos números también alimentan los rangos de color de cada widget del dashboard (barras de almacenamiento, anillos de CPU/memoria, chips de temperatura, el punto del modal de disco), de forma que la clasificación visual en cualquier punto del Monitor mapea a un rango definido respecto al par configurado.
### Duración de dismiss por evento con badge Permanent
El botón *Dismiss* en cada alerta del Health Monitor abre ahora un pequeño dropdown con tres opciones:
- **24 hours** — default anterior, se comporta exactamente como antes
- **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.
### Panel Active Suppressions en Settings
Una nueva sección dentro de **Settings → Health Monitor**, justo debajo de las duraciones de supresión por categoría, lista cada alerta actualmente silenciada — tanto los dismisses con tiempo limitado (con un badge *22h remaining* / *6d remaining*) como los permanentes (con el badge ámbar *Permanent* del dashboard). Cada fila lleva el `error_key`, la categoría, la severidad, el timestamp en que se registró el dismiss, y un botón **Re-enable** que limpia el acknowledgment server-side. Los re-enables están **encolados** — pulsar el botón marca la fila en verde con texto tachado y cambia el botón a *Undo*, y el `POST /api/health/un-acknowledge` real solo se dispara cuando el usuario pulsa **Save**, de modo que un lote de re-enables se entrega atómicamente junto a cualquier cambio pendiente de dropdown por categoría. La acción está protegida por el toggle de *Edit* del Health Monitor en la parte superior de la card. Los dismisses permanentes **solo pueden revertirse desde aquí** — el dashboard no expone intencionadamente un affordance per-alerta de un-dismiss para evitar re-enables accidentales, por lo que el panel de Settings es la superficie deliberada de auditoría + revert para ellos. La lista también se refresca automáticamente cuando se hace un dismiss de una alerta desde el modal del Health Monitor mientras la página de Settings ya está abierta, vía un evento `health-suppression-changed` del navegador más listeners en `window focus` y `document visibilitychange`.
### Tiers de severidad de Disk I/O
Una ventana deslizante de 24 h clasifica ahora los errores ATA / SCSI de dmesg en tres buckets: silencioso (010 eventos transitorios), WARNING (11100) y CRITICAL (100+, o cualquier error duro como UNC / Buffer I/O / Sense Key Hardware Error). Los días tranquilos se quedan tranquilos, pero un único evento de Buffer I/O sigue paginando inmediatamente.
---
## 📨 Canal de notificación Apprise — Paridad completa de features
La integración Apprise que aterrizó como un adapter básico en 1.2.1.4-beta se ha graduado a paridad completa con los canales nativos. Una sola URL de Apprise llega ahora a alrededor de 80 servicios de notificación (Pushover, ntfy, Slack, Matrix, mailto, signal, Pushbullet, Mattermost, Microsoft Teams vía webhooks, …) sin que ProxMenux necesite un adapter dedicado para cada uno. La pestaña Apprise en Notifications expone los mismos controles que Telegram, Gotify, Discord y Email:
- El bloque **Notification Categories** completo — las mismas 10 categorías con sus sub-toggles por evento, idéntico a los otros canales
- **Quiet Hours** — ventana start/end por canal, con el mismo comportamiento de buffering (los eventos disparados durante la ventana se persisten en SQLite y se liberan como un resumen agrupado cuando la ventana cierra, en lugar de dropearse silenciosamente)
- **Daily Digest** — entrega opt-in de un resumen una vez al día a una hora elegida
El filtrado per-channel del backend ya aplicaba genéricamente a cada canal incluyendo Apprise vía el bloque `channel_overrides` — la UI simplemente no estaba surfaceando los controles.
Tres fixes de fiabilidad shipean junto, todos surfaceados después del rollout beta inicial:
1. **Mobile overflow** en viewports estrechos. La fila de Apprise URL solía romper el diseño — el placeholder empaquetaba cuatro URLs de ejemplo completas en una línea y los `<code>` inline de los ejemplos no tenían regla `break-all`. El placeholder es ahora un único ejemplo conciso (`tgram://bottoken/ChatID`), el wrapper del input URL fuerza `min-w-0 / flex-1 / shrink-0` en sus children, y el párrafo de ejemplos usa `break-all min-w-0` para que envuelva limpiamente a cualquier ancho.
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.
---
## 📦 Detección de updates en LXC
Una nueva sección dedicada en **Settings** (entre *Health Monitor Thresholds* y *Notifications*) con un único toggle que protege el scan per-CT de `apt list --upgradable` / `apk list -u` end-to-end. Default ON. Cuando está OFF el scan para completamente (sin llamadas `pct exec`), cada entrada `type=lxc` se purga inmediatamente del registry de managed-installs, y el toggle de notificación correspondiente en *Notifications → Services* desaparece de la UI mientras preserva su preferencia almacenada.
El checker lee también el `mtime` de la caché de metadata del package-manager de cada CT y dispara `apt-get update` / `apk update` desde fuera vía `pct exec` si tiene más de 24 h, con timeout de 60 s y fallo silencioso. Los CTs appliance long-running cuyas cachés estaban meses obsoletas surfacean por fin su backlog real upstream — un CT Debian 12 con caché de 524 días pasó de "0 updates" a "117 (12 security)" en hardware de lab.
---
## 🐧 Coral TPU en LXC — Últimos drivers upstream
El instalador de Coral para LXC (`scripts/gpu_tpu/install_coral_lxc.sh`) se ha reescrito end-to-end para instalar el **último driver `gasket-dkms` upstream** y el **último runtime `libedgetpu1`** (220 líneas añadidas, 150 eliminadas). Los módulos Coral M.2 / mPCIe que antes fallaban al compilar en kernels PVE 9 ahora instalan y bindean limpiamente. Las notificaciones de update registry-driven que aterrizaron en 1.2.1.2 mantienen ambos paquetes frescos en adelante: la pestaña Hardware + Notifications señalizan cuando feranick/gasket-driver publica una release nueva, y el runtime `libedgetpu1` tracked vía `apt` recibe el flujo estándar de System Updates.
El **path de uninstall del instalador Coral** acompañante también aterriza en este ciclo — espejando el flujo NVIDIA para que una instalación Coral pueda revertirse limpiamente si el usuario re-despliega el host sin aceleración TPU.
---
## ⚡ Optimizaciones de rendimiento del Monitor
Una corrida de strace + sampling de 10 minutos sobre un host live surfaceó tres sitios donde el scanner de background del Monitor estaba spawning subprocesos más agresivamente de lo necesario. Los tres están arreglados en 1.2.2:
### Tormenta de subprocesos fail2ban
En hosts donde `fail2ban-client` no estaba instalado, el wrapper de caché alrededor de `_f2b_get_banned_ips()` solo actualizaba su timestamp en éxito. Cada request HTTP al dashboard caía a través del check de caché y disparaba un `execve("fail2ban-client", ...)` fresco que inmediatamente fallaba con `ENOENT` — 250+ llamadas `execve` fallidas en una ventana de 10 minutos. `shutil.which('fail2ban-client')` se resuelve ahora **una vez** al cargar el módulo y el timestamp de caché se actualiza incondicionalmente. Los hosts sin Fail2Ban tienen ahora cero syscalls de `fail2ban-client` por request.
### Colisión del scheduler de smartctl
El polling de temperatura SMART de discos, la lectura de temperatura CPU y la probe de latency solían dispararse en el mismo offset dentro de cada minuto, produciendo un spike medible de CPU / IO cuando todos sus subprocesos spawneaban juntos. Las polls están ahora staggered (latency primero, luego temperatura CPU, luego SMART de disco) preservando la cadencia per-disco de 60 s — el spike ha desaparecido, el CPU total bajo carga no cambia.
### Subproceso de inventario LXC
El mount monitor solía llamar `lxc-info -n <vmid> -p` por cada CT corriendo solo para obtener su PID init. Ahora lee `/proc/<lxc-start-pid>/task/<lxc-start-pid>/children` directamente y solo cae a `lxc-info` cuando la lectura de `/proc` falla. Un subproceso por CT por ciclo de scan eliminado — medible en hosts con 20+ contenedores.
---
## 🔌 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).
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.
---
## 🐧 Health Monitor — Detección de kernel updates en PVE 9.x (#208)
En hosts Proxmox VE 9.x, la fila *System Updates → Kernel / PVE* reportaba "Kernel/PVE up to date" incluso cuando un update para el kernel corriendo estaba esperando upstream. Tres causas combinadas, tres fixes combinados:
1. **La lista de prefijos de kernel-packages** incluye ahora `proxmox-kernel-*` y `proxmox-firmware-*` — PVE 9.x shipea kernels bajo `proxmox-kernel-`, no el prefijo `pve-kernel-` de 7.x / 8.x. El regex anterior nunca matcheaba los paquetes nuevos y por tanto nunca flagueaba ningún update de kernel en 9.x.
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.
---
## 🟢 Instalador NVIDIA
Varias mejoras impulsadas por los reports detallados de campo de [@ghosthvj](https://github.com/ghosthvj) sobre configuraciones de GPU mixta (ver Acknowledgments):
- **Ventana de compatibilidad de kernel** — el menú de versiones respeta ahora el rango de drivers compatibles del kernel corriendo, ofreciendo solo branches que no fallarán al compilar contra el kernel del host.
- **Soporte Alpine LXC** — el install de userspace container-side se reescribió para que succeda en hosts Alpine; la detección de espacio libre funciona fiablemente en todos los layouts de almacenamiento (LVM-thin, ZFS, directory, etc).
- **NVENC patch awareness** — cuando el host tiene el patch NVENC aplicado, el menú de versiones se estrecha a drivers soportados por el patch para que reinstalar nunca lo pierda silenciosamente.
- **Feedback de uninstall** — el path de uninstall reporta ahora un mensaje claro de completación en lugar de volver al menú en silencio.
---
## 🌐 Sitio de documentación — Migración i18n completa
El sitio de documentación acompañante (proxmenux.com) shipea ahora bajo URLs prefijadas por locale (`/en/...` y `/es/...`) con la plumbing de next-intl. Cada doc page es bilingüe — 107 páginas traducidas al español (sin placeholders copy-of-English). El root `/` redirige a `/en/` vía meta-refresh + JS para que la URL apex siga resolviendo a algo útil. Los RSS feeds funcionan per-locale en `/en/rss.xml` y `/es/rss.xml`, con el canonical `/rss.xml` conservado para backwards compatibility con suscriptores de feed existentes. La búsqueda client-side está wireada vía **Pagefind** — el índice se construye fresh en cada deploy de CI desde el HTML output final y se descarga fragmentariamente por el cliente, así que la búsqueda funciona sin un servidor backend.
Nuevas páginas de documentación cubren la sección **Active Suppressions** en la pestaña Settings y el **dropdown Dismiss por evento** en el modal del Health Monitor, ambas con capturas reflejando la nueva UI.
---
## 🔧 Otras mejoras
- **Sección AI Enhancement en Notifications** — reescrita de una fila uppercase atenuada que los testers consistentemente scrolleaban sin ver, a un label foreground normal-case con un icono `Sparkles` líder y un badge persistente (verde *Active* cuando IA está habilitada, neutro *Optional* cuando no lo está) para que la feature sea descubrible independientemente del estado.
- **Monitorización de temperatura de discos** — readings mejorados, caching más inteligente entre probes SMART, y un modal de historia rediseñado que abre a 24 h por defecto con estadísticas min / avg / max.
- **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.
- **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.
- **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.
---
## 🙏 Acknowledgments
Esta release incluye contribuciones de código directas de la comunidad y una cantidad sustancial de feedback que dio forma al diseño. Particular agradecimiento a:
### Contributors de código
**[@jcastro](https://github.com/jcastro)** entregó cinco mejoras directas que shipean con v1.2.2:
- **Selección de ISOs de VM desde todos los almacenamientos ISO** — nuevo helper compartido `scripts/global/iso_storage_helpers.sh` más integración en `vm_creator.sh`, `select_linux_iso.sh` y `select_windows_iso.sh`. El picker de ISO lee ahora desde cada almacenamiento Proxmox tagueado como ISO content en lugar de estar pinned a `local`. Commit [`092b548d`](https://github.com/MacRimi/ProxMenux/commit/092b548d).
- **Selector de canal de release en Settings** — un menú proper bajo `scripts/menus/config_menu.sh` para flipear entre los canales de install estable y beta in-place, con la gestión correcta de `version.txt` / `beta_version.txt` en cada lado. Commit [`f8a8c43d`](https://github.com/MacRimi/ProxMenux/commit/f8a8c43d).
- **ZFS autotrim en el auto post-install** — `auto_post_install.sh` habilita ahora `autotrim=on` en pools ZFS root por defecto (con el disable correspondiente en el path de uninstall), de forma que installs SSD-backed reclaman espacio liberado sin intervención manual. Commit [`8877f987`](https://github.com/MacRimi/ProxMenux/commit/8877f987).
- **Detección de webhook loopback + handoff de update** — `flask_notification_routes.py` clasifica correctamente webhooks de `127.0.0.1` / `localhost` como loopback, y el handoff de update del script `menu` ya no flackea en edge cases. Commit [`70ab072c`](https://github.com/MacRimi/ProxMenux/commit/70ab072c).
- **Figurine bumped a 2.0.0** — refresh del banner tool en `customizable_post_install.sh`, con la página de docs actualizada para matchear. Commit [`aba94028`](https://github.com/MacRimi/ProxMenux/commit/aba94028).
**[@pespinel](https://github.com/pespinel)** arregló una regresión del beta-installer que rompía los paths de servicio tras el move al nuevo layout runtime — `install_proxmenux_beta.sh` resuelve ahora los paths correctos de la unit systemd en first install y en update. Commit [`0daab74a`](https://github.com/MacRimi/ProxMenux/commit/0daab74a).
### Field reports que dieron forma al trabajo de GPU + Coral
Los reports detallados y sugerencias de **[@ghosthvj](https://github.com/ghosthvj)** sobre el flujo de hardware passthrough impulsaron las mejoras de scripts de GPU en esta release. Los fixes del instalador NVIDIA, el hardening del lifecycle de GPU + audio acompañante en `switch_gpu_mode.sh`, y el checklist de audio-companion iGPU en `add_gpu_vm.sh::detect_optional_gpu_audio` empezaron todos desde sus reports de edge cases que los paths de código previos manejaban pobremente.
### Todos los demás
Un gracias enorme a cada usuario que abrió un issue en GitHub, comentó en [GitHub Discussions](https://github.com/MacRimi/ProxMenux/discussions), reportó un bug en el canal de la comunidad, o pasó a compartir qué funcionaba y qué no en su hardware. **Muchas de las mejoras internas en esta release — el stagger del scheduler de smartctl, el fix de caché de fail2ban, el reemplazo `lxc-info /proc`, el handshake del terminal HTTPS, la detección de kernel-update en PVE 9.x, todo el wiring de Apprise — empezaron como un report de alguien encontrándose con el problema.** Seguid llegando.
---
## 2026-04-20
### Nueva versión ProxMenux v1.2.1 — *SR-IOV Awareness & GPU Passthrough Hardening*
+1 -1
View File
@@ -1 +1 @@
1.2.1
1.2.2
@@ -25,6 +25,7 @@ interface Contributor {
roleKey: "testing" | "testingReviewer"
avatar: string
youtubeUrl?: string
githubUrl?: string
}
const contributors: Contributor[] = [
@@ -48,6 +49,7 @@ const contributors: Contributor[] = [
roleKey: "testingReviewer",
avatar: "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/avatars/jonatancastro.png",
youtubeUrl: "https://www.youtube.com/@JonatanCastro",
githubUrl: "https://github.com/jcastro",
},
{
name: "Kamunhas",
@@ -152,7 +154,22 @@ export default async function ContributorsPage({ params }: { params: Promise<{ l
<FlaskRound className="h-4 w-4 text-white" aria-hidden />
</div>
</div>
<h3 className="text-base font-semibold text-gray-900 mt-2 mb-0">{c.name}</h3>
<h3 className="text-base font-semibold text-gray-900 mt-2 mb-0">
{c.githubUrl ? (
<a
href={c.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 hover:text-blue-700 hover:underline"
title={`${c.name} on GitHub`}
>
{c.name}
<ExternalLink className="w-3 h-3" />
</a>
) : (
c.name
)}
</h3>
<p className="text-xs text-gray-600 mt-0.5">{t(`testers.roles.${c.roleKey}`)}</p>
{c.youtubeUrl && (
<a