ProxMenux 1.2.6.2-beta: OCI containers in the Monitor, docs and fixes

OCI manager Apps
- App tab: containers installed from an OCI image are identified from their
  installation record; the application and image versions are shown and an
  update is detected by image digest; repository link; Refresh data.
- Updates tab for OCI containers: Update and Recreate run the same flow as the
  OCI menu in the Monitor terminal; the pre-update backup can be kept in a
  backup storage; scheduled image updates with an optional minimum age.
- Logs tab: console output of the application, kept on the host
  (lxc.console.logfile + logrotate) and followed live.
- The Proxmox console opens a shell (cmode: shell) when the image has one.
- A damaged image download is fetched again before failing.
- Multi-container applications open at their LAN address; volume mount
  points on block storage report their usage.

Monitor
- Proxmox notifications are delivered to a loopback-only HTTP listener when
  HTTPS is enabled, so they no longer fail certificate verification.
- Log persistence counts recurring patterns only; an ended burst is not
  reported as persistent and its warning clears on its own (#386).
- Proxmox notification config backups are deduplicated and capped at three.
- The update icon on the Apps page opens the container on its Updates tab.
- Version 1.2.6.2-beta and its release notes in every Monitor language.

Docs
- OCI manager Apps and Audit & Report rebuilt as per-page message files,
  with a new page for OCI containers in the Monitor.
- Seven pages fixed where rich-text tags were missing from t.rich.

Translations
- Spanish fixes across the OCI engine, the Monitor and the TUI menus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-25 21:51:12 +02:00
co-authored by Claude Opus 5.5
parent 386d33df6e
commit 4437a671d2
524 changed files with 14459 additions and 3841 deletions
+46 -11
View File
@@ -304,21 +304,56 @@ def _df_via_host_pid(host_pid: str, ct_target: str) -> dict[str, Optional[int]]:
["df", "-B1", "--output=size,used,avail", full],
capture_output=True, text=True, timeout=_STAT_TIMEOUT,
)
except subprocess.TimeoutExpired:
# A filesystem that does not answer df will not answer statfs
# either; asking again would only double the wait.
return empty
except OSError:
return empty
if proc.returncode == 0:
lines = [ln for ln in proc.stdout.strip().splitlines() if ln.strip()]
parts = lines[-1].split() if len(lines) >= 2 else []
if len(parts) >= 3:
try:
return {
"total_bytes": int(parts[0]),
"used_bytes": int(parts[1]),
"available_bytes": int(parts[2]),
}
except ValueError:
pass
return _statfs_via_host(full)
def _statfs_via_host(full: str) -> dict[str, Optional[int]]:
"""Capacity of a path read from its filesystem rather than the mount table.
df names a path by finding the mount that holds it in the host's own
table, and a volume of a native OCI container is not in that table: df
answers "no file systems processed" and the tab showed no usage for any
of its volumes. statfs asks the filesystem of the path directly and
returns the same three figures df prints. It is only reached when df
completes without an answer, so every mount df can measure keeps the
value it had.
"""
empty = {"total_bytes": None, "used_bytes": None, "available_bytes": None}
try:
proc = subprocess.run(
["stat", "-f", "-c", "%S %b %f %a", full],
capture_output=True, text=True, timeout=_STAT_TIMEOUT,
)
if proc.returncode != 0:
return empty
lines = [ln for ln in proc.stdout.strip().splitlines() if ln.strip()]
if len(lines) < 2:
return empty
parts = lines[-1].split()
if len(parts) < 3:
return empty
return {
"total_bytes": int(parts[0]),
"used_bytes": int(parts[1]),
"available_bytes": int(parts[2]),
}
block, total, free, available = (int(value) for value in proc.stdout.split())
except (subprocess.TimeoutExpired, OSError, ValueError):
return empty
if block <= 0 or total <= 0:
return empty
return {
"total_bytes": total * block,
"used_bytes": (total - free) * block,
"available_bytes": available * block,
}
def _df_via_pct_exec(vmid: str, ct_target: str,