Skip to content

Release 1.2.2 — version.txt + CHANGELOG (EN/ES) + contributor link#218

Merged
MacRimi merged 22 commits into
mainfrom
develop
Jun 2, 2026
Merged

Release 1.2.2 — version.txt + CHANGELOG (EN/ES) + contributor link#218
MacRimi merged 22 commits into
mainfrom
develop

Conversation

@MacRimi
Copy link
Copy Markdown
Owner

@MacRimi MacRimi commented May 31, 2026

Summary

Final ingredient of the v1.2.2 stable release. Do not merge before Tuesday June 2 — by design, this is the moment the stable channel's update notifier picks up 1.2.2, so the merge timing matters.

What this PR does

Acknowledgments included in the CHANGELOG

  • @jcastro — 5 direct commits (ISO storage selector, release channel switcher, ZFS autotrim, webhook loopback detection, Figurine 2.0.0)
  • @pespinel — 1 commit (beta installer runtime path fix)
  • @ghosthvj — field reports that shaped the GPU + Coral hardware work
  • Plus a general thanks to everyone who reported issues / commented in Discussions

Workflows that will run after merge

  • `deploy.yml` — auto-triggers because the PR touches `CHANGELOG.md`, `lang/` and `web/`. Will publish the new docs site with v1.2.2 changelog (EN + ES), the new contributor link, and (importantly) the corrected trailing-slash behaviour from the cumulative release prep.
  • `build-appimage.yml` — does NOT trigger here (PR doesn't touch `AppImage/**`).

After merge — manual follow-ups

  1. Wait for `deploy.yml` to publish (typically 2–3 min on success).
  2. Verify `https://proxmenux.com/en/changelog/\` shows v1.2.2 at the top.
  3. Verify `https://proxmenux.com/es/changelog/\` shows the Spanish v1.2.2 entry.
  4. Create git tag `v1.2.2` on the merge commit.
  5. Open `https://github.com/MacRimi/ProxMenux/releases/new\` → target `v1.2.2` → paste the contents of `release.md` (lives in develop, never in main — per the project convention).

Test plan

  • `version.txt` content verified locally: `1.2.2`
  • `CHANGELOG.md` validates as markdown (1239 lines, well-formed headings)
  • `lang/es/CHANGELOG.md` validates as markdown (1240 lines, well-formed headings)
  • Local web build with all the pending changes: `cd web && npm run build` → 232 pages indexed, no errors
  • Contributor page renders the GitHub link icon next to Jonatan Castro

MacRimi and others added 22 commits May 31, 2026 19:14
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>
The drafting date (today) was used as a placeholder. The actual
release date is Tuesday 2026-06-02, when PR #218 merges and the
update notifier picks up 1.2.2. Aligns the changelog header in both
EN and ES with the publication date users will see.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…menu

Three changes that fold into the v1.2.2 release PR:

1. AppImage: bump Next.js 15.1.6 -> 15.1.9 (CVE-2025-55182)
   GHSA-9qr9-h5gf-34mp / React2Shell is a pre-auth RCE in React Server
   Components when Server Functions deserialize attacker payloads. The
   ProxMenux Monitor ships Next.js in `output: "export"` mode behind
   Flask on :8008, so there is no runtime Next.js server and no
   "use server" directive in the source tree — the exploitable path is
   not reachable. Bumping to 15.1.9 anyway because OpenVAS and similar
   scanners flag the version string from the JS bundle regardless of
   architecture; raising the floor removes false-positive noise across
   every install. Reported by @rost43 in #219.

2. web/components/ui/doc-navigation.tsx: handle sidebar entries that
   point to in-page anchors. The Storage Share Manager sidebar has
   entries for `/docs/storage-share#host` and
   `/docs/storage-share#lxc-net` as section headers, but
   usePathname() does not include the hash so every visit collapsed
   to the parent page. As a result Next/Previous on /docs/storage-share
   stayed stuck at #host, and Next from .../lxc-mount-points/ pointed
   back at #host instead of #lxc-net. Read window.location.hash on
   mount (and on hashchange) and try the pathname+hash match before
   falling back to the pathname-only lookup. SSR hydrates with an
   empty hash and refreshes once mounted — brief render before
   hydration is the same as the previous behaviour, so no regression.

3. scripts/help_info_menu.sh: user-side improvement (mirrored from
   develop).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…odules

The previous bump commit (2f24de2) shipped a binary that still carried
Next.js 15.1.6 in the bundled chunks even though AppImage/package.json
was at 15.1.9. Root cause: build_appimage.sh only ran `npm install`
when `node_modules` did not exist; on the .50 build host node_modules
had been cached since the 1.2.1 build cycle, so the bump was silently
ignored and the build re-used the stale tree.

Fix the script: always run `npm install --legacy-peer-deps` on every
build. npm reconciles against the lockfile in under a second when
everything is already in sync, so the change is free on a warm tree
and correct on a stale one.

Rebuild from a clean node_modules on .50, redeploy to all four hosts
(SHA 4602b8d4aa130c6f...), runtime grep confirms the bundle now
contains 15.1.9 with no traces of 15.1.6 left. Same architecture and
threat model as before — Flask serves the static export on :8008,
no Next.js runtime — but the version banner now matches the lockfile.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a host gets transient I/O events on a disk while smartctl is
momentarily unavailable (the canonical case: late in a noisy
shutdown), the disk-scan code records a `disk_<name>` WARNING tagged
"SMART: unavailable" exactly once and trusts the next scan to clear
it. That trust is misplaced: the clear path only fires when the
device shows up in the current dmesg window with zero events. After
a reboot, dmesg is empty for that device — so the device never gets
iterated, resolve_error is never called, and the dashboard stays
orange for a disk whose SMART now reports PASSED.

Caught on a lab host where `disk_nvme2n1` had been stuck as WARNING
for hours after a reboot. SMART was 100% healthy at the moment of
inspection (Critical Warning 0x00, 0 media errors, 100% spare). The
error's first_seen and last_seen were identical and pre-dated the
current boot, confirming a one-shot record that nothing had cleared.

Fix: add a `_reconcile_stale_disk_warnings()` pass at the top of
`_check_disks_optimized()`. For every active `disk_*` error
(skipping `disk_fs_*`, which is already reconciled separately):

  - device gone from /dev/   → resolve "Device no longer present"
  - device present + SMART PASSED → resolve "Transient I/O cleared,
    SMART now reports healthy"
  - device present + SMART UNKNOWN/FAILED → leave active so the
    main loop can re-classify on the next dmesg window

Acknowledged errors are left alone so the user's explicit dismiss
intent isn't overridden.

Verified end-to-end: re-injected the original `disk_nvme2n1`
warning into the persistence DB on the lab host, waited one scan
cycle, error was resolved automatically with `resolved_at` set and
`resolution_reason = 'Transient I/O cleared, SMART now reports
healthy'`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The UPDATE in `_resolve_error_impl` only touched `resolved_at` — the
`reason` argument every caller passes was silently dropped, and the
`resolution_reason` / `resolution_type` columns stayed NULL for every
auto-resolved error. The columns were added back in a previous sprint
for exactly this audit-log purpose, but the writer was never updated
to populate them.

Fix the SQL to write `resolution_reason = ?` and tag
`resolution_type = COALESCE(existing, 'auto')` so admin-cleared
errors (whose type is set elsewhere) keep their value while the
default auto path correctly labels itself.

Verified end-to-end on the lab host: re-injected the `disk_nvme2n1`
warning, waited one scan cycle, the row now reads
`resolution_type='auto'` and
`resolution_reason='Transient I/O cleared, SMART now reports healthy'`
— previously these columns stayed NULL even though the resolve_error
call passed a descriptive reason.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`getDiskHealthBreakdown` carried its own hardcoded ladder (HDD ≤45
normal, ≤55 warning) that was much stricter than the configurable
defaults consumed by `getTempColor` via `useDiskTempThresholds`
(HDD warn 60, hot 65). HDDs at 48 °C therefore rendered a green
"Healthy 48°C" badge on the card but were tallied as "warning" in
the top-of-page "X normal, Y warning, Z critical" summary, leaving
the user with the misleading "6 normal, 5 warning" line.

Use the same threshold map as the per-disk badge so the colour and
the count are always consistent, and so Settings → Health Monitor
Thresholds → Disk temperature actually applies to the breakdown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The tracked binary still pointed at the build made before the
last two fixes landed (resolution_reason persistence in
health_persistence and disk-temp breakdown alignment in
storage-overview). Re-build the AppImage so the GitHub-published
binary matches what is actually running on the deploy targets.

New SHA-256:
  d043e2f27f21315931ab53d87f02390b1a66b0c1730e8b7699aafb565809efbb

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The v1.2.0 binary lingered in the repo after later releases. Remove
it so AppImage/ holds only the current shipping artefact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`get_disks_observation_counts` maps each serial's count to that
serial's "most recent" device_name (so renames like ata8 -> sdh keep
the badge attached). When several physical disks have passed through
the same kernel name across reboots — common with NVMe, the kernel
probes in a different order depending on which slots are populated —
disk_registry keeps a row per (device_name, serial) seen and the
"most recent" device_name for a serial can now be in use by an
entirely different disk.

Concrete case from the wild: serial 211716800490 was nvme0n1 during
the previous boot and earned a real I/O observation. After removing
four of five NVMes, the surviving disk (serial 243332800236) booted
into nvme0n1. The badge layer mirrored 211716800490's count onto
nvme0n1 — which is now a different physical disk — and showed
"1 obs." on the wrong drive, while the modal (which scopes by the
current (device_name, serial) registry row) found nothing and
rendered an empty history.

Only mirror a serial's count onto its device_name when that
device_name is currently owned by the same serial, determined from
the freshest disk_registry row. The serial-keyed entry stays
unconditional so observations remain reachable when the disk is
re-plugged under another device name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New build picks up the get_disks_observation_counts NVMe-rename fix.

SHA-256:
  3b44eb1172b4b1b7e6a36d1c9f1cd5a237ec04d52543bb791358525b0653a402

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updated contributors image link to include version parameter.
A mass-backup webhook that exceeded ~2 KB used to be silently
truncated by `desc = message[:MAX_EMBED_DESC]` with MAX_EMBED_DESC
set to 2048 — half of Discord's real description limit and far
below what a multi-VM backup digest produces. The trailing jobs
just vanished from the channel.

Bring the channel up to Discord's actual webhook contract:

* description limit raised to the real 4096-char cap
* if the body still doesn't fit, split it on line boundaries into
  one embed per chunk so every backup entry is preserved
* keep title + fields on the first embed only; attach the footer
  and timestamp to the last embed so the rendered card has the
  normal head/tail framing even when split across many embeds
* enforce Discord's 6000-char-per-embed cap (title + description +
  every field name+value) — only kicks in when many large fields
  combine with a chunk already near the description ceiling
* batch up to 10 embeds per webhook POST (Discord's per-message
  limit) and POST additional messages sequentially with a 0.4 s
  gap so a >10-embed digest doesn't trip the 5/2 s webhook rate
  limit

Verified with synthetic mass-backup payloads:
* 14 KB / 200 jobs → 4 embeds, 1 POST
* 60 KB / 60 lines → 15 embeds, 2 POSTs (10 + 5)

New AppImage SHA-256:
  16ad59ea63a64e5be460cd73f87315e8b39b756bf1c61f3cb2019e9fa3e76361

Closes #220.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…_host

Two release-day fixes in the host-side share tooling, both reported
during testing of the v1.2.2 candidate.

lxc-mount-manager_minimal.sh
  After adding a mount point on a stopped LXC the script offered to
  `pct reboot $ct` unconditionally — which fails on a stopped CT
  because `pct reboot` only accepts running ones, so the user saw a
  bogus "Failed to restart" right after a successful mount. Gate the
  prompt on `pct status` and, when the CT is stopped, tell the user
  the mount will activate on next start instead of trying to reboot
  it. The matching restart prompt in the remove flow (around line
  540) was already doing the check correctly; this just brings the
  add flow in line.

disk_host.sh
  The script always registered the disk as a Proxmox storage via
  `pvesm add dir|zfspool`. nfs_host.sh and samba_host.sh already
  offered a dual-flow chooser ("Proxmox storage" / "host fstab only"
  / both) so a user could mount the share on the host for LXC
  bind-mounts without surfacing it as a Proxmox storage. Replicate
  that chooser for local disks:

  * new `select_mount_method` checklist with `pvesm` and `fstab`,
    inserted after filesystem selection. ZFS is forced into the
    pvesm path because a ZFS pool can't be expressed as an fstab
    mount.
  * `configure_disk_storage` skips the Content Types prompt when
    only fstab is selected and renames "Storage ID" → "Mount Name"
    in the same case so the wording matches what the user will
    actually see (or not see) in Proxmox.
  * `format_and_mount_disk` title and summary lines adapt to the
    chosen mode.
  * the trailing `add_proxmox_dir_storage` call in `add_local_disk_storage`
    runs only when `MODE_PVESM=1`; in fstab-only mode the final
    message points users at the LXC Mount Manager for bind-mounts.

  Verified end-to-end on a 32 GB USB disk against LXC 112
  (unprivileged) on .55: fstab-only path → bind-mount → root inside
  CT writes mapped to host uid 100000, regular user writes mapped to
  host uid 101000, both reads/writes successful from inside the
  container.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related gaps in the disk-host script that surfaced while testing
the new dual-flow (pvesm / fstab-only / both) added earlier today.

view_disk_storages used to read only from `pvesm status`, so a disk
added via the new fstab-only path — exactly the case where the user
wants a local disk available for LXC bind-mounts without registering
it as a Proxmox storage — never showed up. Replicate the same fstab
scan remove_disk_storage already performs and list those mounts in a
second section underneath the pvesm ones. Empty state and wording
updated so the panel no longer claims "No local storage configured
in Proxmox" when fstab-only mounts are present.

Both view_disk_storages (new code) and remove_disk_storage (existing
fstab branch) were happily picking up `/mnt/Archivos` and any other
CIFS/NFS share mounted under /mnt. samba_host.sh and nfs_host.sh own
those — surfacing them in the local-disk menus would let a user
remove a network share from the wrong screen. Filter by fstype
(skip cifs/smbfs/nfs/nfs4/nfsv4/sshfs/fuse) and additionally require
that the resolved source be a real block device, which drops bind
mounts and anything else whose backing source isn't a disk.

Verified on .55 with the test disk-sda fstab-only mount alongside
the existing //192.168.0.15/Archivos CIFS mount: only disk-sda is
listed by the local-disk view/remove flows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The fstab-only mount method explicitly says "for LXC bind-mounts" in
its dialog wording, but the mount point left behind by mkfs +
mkdir is owned root:root with mode 0755. An unprivileged LXC sees
the directory through its uid offset (root inside → host uid 100000)
which lands under the directory's "others" bits — so the container
can read but never write, and the user has to track down the
chmod / setfacl step manually.

lxc-mount-manager_minimal.sh already offers exactly this fix as
`lmm_offer_host_permissions` when the user adds the bind-mount
through that script, but the disk-side script never closed its half
of the loop. Add a small `_apply_lxc_bind_mount_perms` helper that
runs `chmod o+rwx` plus `setfacl o::rwx + default ACL` whenever
MODE_FSTAB=1, and call it from both `mount_disk_permanently`
(format path) and `mount_existing_disk` (use_existing path). Pure
pvesm-only mounts keep the original behaviour — chmod o+rwx on a
VM/backup storage isn't desirable.

Verified on .55 against the existing /mnt/disk-sda + LXC 112
(unprivileged): unprivileged container root could not write before
(Permission denied), writes succeed after the perms are applied and
land on the host as uid 100000 as expected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The earlier fix to handle stopped containers in the add flow used
`msg_info` for the "Container is stopped — mount will activate
automatically on next start" line. `msg_info` is the spinner-start
half of the msg_info/msg_ok pair — it never gets closed here
because there's nothing to wait on, so the spinner glyph stays
visible (⠋) right before the "Press Enter to continue" prompt.

Switch to `msg_ok` so the line renders as a clean static success
mark, matching the visual style of the other terminal messages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related improvements to the post-add verification step that the
user hit while testing the stopped-CT case.

A stopped container couldn't be probed at all — the previous patch
just told the user "mount will activate on next start" and left
them to discover any issues later (the typical issue being
permission denied on the host directory, since the dialog confirms
the bind-mount was added but never proves it works). Offer to start
the container right now so the user gets feedback in the same
session; if they decline, fall back to the informational line.

The post-restart / post-start probe used `test -d $ct_mount_point`
which only checks that the directory is visible inside the
container. That always succeeds whenever the bind-mount took
effect, even if the host directory permissions don't let the
unprivileged-LXC mapped uid write — exactly the case the user just
ran into with /mnt/disk-sda (700 → others gets r-x). Replace with a
touch+rm probe in a new `_lmm_verify_writable` helper used by both
branches so the user is told straight away when writes will fail
and, when they will, is given the exact `chmod o+rwx` / `setfacl`
command and a pointer to the host-perms prompt.

Verified on .55 / LXC 112 (unprivileged) against /mnt/disk-sda:
container stopped → start prompt → start → directory visible →
touch probe → success.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The HOST block already groups everything that runs on the host
side, so the secondary "Host-only resources" divider between item 4
and item 5 was visual noise — the user reading the menu sees two
HOST sub-titles back to back and has to wonder how items 1-4 differ
from item 5 in scope. They don't; items 1-4 register an external or
local resource as a Proxmox storage, item 5 creates a local shared
directory. Both are host-side actions.

While here, retitle item 5 from "Add Shared Directory on Host" to
"Create Shared Directory on Host" — items 1-4 add an existing
external/local resource, item 5 creates a new one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MacRimi MacRimi merged commit 98e9fb3 into main Jun 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant