Commit Graph

844 Commits

Author SHA1 Message Date
MacRimi
63f82d971d update 1.2.2.3 beta 2026-07-06 12:00:43 +02:00
MacRimi
9c90722ef9 update 1.2.2.3 beta 2026-07-06 00:59:50 +02:00
MacRimi
7156af1965 update 1.2.2.3 beta 2026-07-06 00:39:43 +02:00
MacRimi
8df8a77bf1 update 1.2.2.3 beta 2026-07-06 00:30:47 +02:00
MacRimi
487ab04a14 update 1.2.2.3 beta 2026-07-05 23:58:58 +02:00
MacRimi
26b47e63d9 host-backup(pbs): fix scheduled-job encryption + surface real key import error + pass --keyfile on downloads
Three bugs against the PBS encryption flow:

1. Create-scheduled-job with encryption failed with "Recovery setup
   failed: no PBS keyfile present" whenever the operator picked
   "Generate a new keyfile" but had no keyfile installed yet. The
   frontend called /pbs-recovery/setup before creating the job, but
   the keyfile was only materialised later during job creation. The
   endpoint now generates the keyfile atomically if missing before
   building the escrow blob — same prompt-first order the CLI wizard
   applies. Existing keyfiles are still trusted and never rotated.

2. Importing a valid PBS keyfile via the Web dialog returned a
   generic "did not recognise this file as a valid PBS keyfile" that
   hid the real reason (kdf mismatch, missing passphrase, corrupt
   JSON, ...). The endpoint now attaches the stderr of
   `proxmox-backup-client key info` as `tool_output` and the frontend
   renders it verbatim inside the red banner. Also strips a leading
   UTF-8 BOM before validating so an editor-inserted BOM stops being
   silently classified as "invalid keyfile".

3. Downloading an encrypted PBS snapshot failed with "missing key —
   manifest was created with key XX:XX:..." even when the correct
   keyfile was installed at /usr/local/share/proxmenux/pbs-key.conf,
   because the restore worker invoked `proxmox-backup-client restore`
   without `--keyfile`. The flag is now passed whenever a local
   keyfile exists (PBS ignores it for unencrypted archives). On a
   fingerprint mismatch the error now appends the installed key's
   fingerprint so it can be compared side-by-side with the manifest's
   expected value — same fingerprint also exposed via
   /pbs-recovery/status for the UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-05 23:14:52 +02:00
MacRimi
5b22039600 Update 1.2.2.3 beta 2026-07-05 17:23:00 +02:00
MacRimi
7fc7125c71 create 1.2.2.3 beta 2026-07-05 16:50:06 +02:00
MacRimi
29bca610a0 update 1.2.2.2 beta 2026-07-05 09:30:13 +02:00
MacRimi
66dd3ec014 update 1.2.2.2 beta 2026-07-04 21:37:39 +02:00
MacRimi
35cb10ff44 update 1.2.2.2 beta 2026-07-03 18:59:45 +02:00
MacRimi
c455d66b91 update 1.2.2.2 beta 2026-07-02 23:52:04 +02:00
MacRimi
bc3c771137 update 1.2.2.2 beta 2026-07-02 18:12:13 +02:00
MacRimi
f2b0b1b039 Update notification_templates.py 2026-07-01 20:54:52 +02:00
MacRimi
33a8f4baa5 update 1.2.2.2 beta 2026-06-30 17:58:32 +02:00
MacRimi
b2753be204 update 1.2.2.2 beta 2026-06-28 15:17:30 +02:00
MacRimi
51b9285980 update 1.2.2.2 beta 2026-06-28 13:06:40 +02:00
MacRimi
ecfdcf1bac update 1.2.2.2 beta 2026-06-26 11:11:39 +02:00
MacRimi
63b9d69f3f update 1.2.2.2 beta 2026-06-26 10:25:06 +02:00
MacRimi
a56afecccf update 1.2.2.2 beta 2026-06-25 17:29:55 +02:00
MacRimi
484f0ce897 update 1.2.2.2 beta 2026-06-25 17:07:58 +02:00
MacRimi
cdb4522e5f update 1.2.2.2 beta 2026-06-25 00:26:03 +02:00
MacRimi
202068124b update 1.2.2.2 beta 2026-06-24 23:40:22 +02:00
MacRimi
cd2a075fab update 1.2.2.2 beta 2026-06-24 16:02:35 +02:00
MacRimi
93553574b3 Update 1.2.2.2 beta 2026-06-23 11:19:04 +02:00
MacRimi
68c8c03642 update 1.2.2.2 pre-beta 2026-06-22 11:38:06 +02:00
MacRimi
9d099ba358 Update 1.2.2.2 beta 2026-06-22 00:55:34 +02:00
MacRimi
d807c9f085 Update flask_server.py 2026-06-22 00:14:46 +02:00
MacRimi
09a67bf4e6 update 1.2.2.2 beta 2026-06-21 22:41:54 +02:00
MacRimi
8e92df5bd7 update 1.2.2.2 beta 2026-06-21 00:35:22 +02:00
MacRimi
57e936785d update 1.2.2.2 beta 2026-06-20 21:59:19 +02:00
MacRimi
3fd7f4b2a4 Update 1.2.2.2 beta 2026-06-19 23:38:57 +02:00
MacRimi
c4447eae5e update 1.2.2.2 beta 2026-06-13 19:05:00 +02:00
MacRimi
4dc8be7387 Add beta 1.2.2.2 2026-06-10 19:05:13 +02:00
MacRimi
61ff665cec update beta 1.2.2.2 2026-06-09 00:13:24 +02:00
MacRimi
61ff98e830 Update beta 1.2.2.1 2026-06-06 18:30:11 +02:00
MacRimi
66419777d8 Update beta 1.2.2.1 2026-06-06 11:37:54 +02:00
MacRimi
3191f5250d Update 1.2.2.1 beta 2026-06-05 19:22:07 +02:00
MacRimi
3629fe8848 Add beta 1.2.2.1 2026-06-05 17:12:23 +02:00
MacRimi
5a116e77b9 Discord channel: split oversized digests across embeds (#220)
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>
2026-06-02 17:30:59 +02:00
MacRimi
642bd8ecae health_persistence: stop leaking obs counts across NVMe device renames
`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>
2026-06-01 23:52:11 +02:00
MacRimi
3c5beb0286 Persist resolution_reason on resolve_error so the audit log is useful
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>
2026-06-01 23:02:52 +02:00
MacRimi
9677c5cb19 Health Monitor: reconcile stale disk warnings across reboots
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>
2026-06-01 22:54:14 +02:00
MacRimi
d25faedc2b Rebuild AppImage with actual Next.js 15.1.9 + always reconcile node_modules
The previous bump commit (2f24de25) 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>
2026-06-01 22:37:44 +02:00
MacRimi
4f3750a8ab Fix CI: add pagefind to web devDeps + portable AppImage cache path
Two regressions surfaced after the 1.2.2 release merge to main, both
in workflows that auto-trigger on push to main:

* Deploy to GitHub Pages — build failed with `pagefind: not found`
  (exit 127) after Next.js prerendered all 241 routes. pagefind was
  not declared in web/package.json; the local build only worked
  because the project root had its own package.json with pagefind
  as a devDep (the one we just gitignored in the previous commit).
  Add `pagefind: ^1.5.2` to web/package.json devDependencies and
  regenerate web/package-lock.json so `npm ci` in CI puts the
  binary at web/node_modules/.bin/pagefind.

* Build ProxMenux Monitor AppImage — failed at the first step with
  `mkdir: cannot create directory '/var/cache/proxmenux-build':
  Permission denied`. The cache path was hardcoded to /var/cache/,
  which is writable when the script runs as root (the .50 host
  manual build) but not as the unprivileged GitHub Actions runner.
  Switch to `${XDG_CACHE_HOME:-$HOME/.cache}/proxmenux-build/` —
  works identically in both environments.

Verified locally: `cd web && npm ci && npm run build` produces 2804
files in out/, 231 pages indexed by pagefind, root redirect intact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 13:34:30 +02:00
MacRimi
9e8434a16b Release 1.2.2 stable — consolidated v1.2.1.x cycle
Promote the v1.2.1.x beta cycle to stable: version markers bumped
from 1.2.1.4-beta to 1.2.2 across version.txt, AppImage/package.json,
flask_server.py (3 places) and the four UI labels in login,
proxmox-dashboard, storage-overview and release-notes-modal.

Replace AppImage/ProxMenux-1.2.1.4-beta.AppImage with
ProxMenux-1.2.2.AppImage and regenerate the .sha256 sidecar
(097e2344675d4b21f1dd18c531c956c299a6507fbc3d0c9695418063581ba2b0).
The new binary is verified on all 4 lab hosts (.50 / .55 / .89 /
1.10) — same sha, all services active, runtime version markers
report 1.2.2.

CHANGELOG["1.2.2"] in release-notes-modal.tsx consolidates every beta
in the 1.2.1.x line (12 added / 13 changed / 18 fixed), and
CURRENT_VERSION_FEATURES is rewritten with the four stable highlights:
Health Monitor Thresholds, granular dismiss control (per-event
duration + Active Suppressions panel), Apprise notification channel
parity, and LXC update detection.
2026-05-31 13:15:39 +02:00
MacRimi
853bcbde35 Fix AppRise 2026-05-31 11:03:04 +02:00
MacRimi
2442ca63be Update AppImage 1.2.1.4 2026-05-31 10:36:16 +02:00
MacRimi
4bf49675d2 Update ProxMenux 1.2.1.4-beta 2026-05-30 21:54:32 +02:00
MacRimi
a3aa5d9c1a Update flask_server.py 2026-05-25 18:01:24 +02:00