mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-29 10:06:41 +00:00
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>
76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Keeping the backup an OCI update takes, in a Proxmox VE backup storage.
|
|
|
|
Every update already stops the container and takes a verified vzdump backup,
|
|
which the rollback uses and which is deleted once the new image works. When
|
|
the backup is to be kept, that same archive is moved into the dump directory
|
|
of the chosen storage instead of being deleted: one backup, not two.
|
|
|
|
A storage without a directory of its own (Proxmox Backup Server) cannot
|
|
receive a file, so a backup is written to it with vzdump before the update.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
from oci_ui import translate
|
|
|
|
STORAGE_RE = re.compile(r'[A-Za-z0-9._-]{1,64}')
|
|
|
|
|
|
def _storage(storage):
|
|
if not STORAGE_RE.fullmatch(storage or ''):
|
|
raise ValueError(translate('Invalid storage name'))
|
|
result = subprocess.run(['pvesh', 'get', f'/storage/{storage}', '--output-format', 'json'],
|
|
capture_output=True, text=True, timeout=30)
|
|
if result.returncode != 0:
|
|
raise ValueError(f"{translate('The backup storage does not exist:')} {storage}")
|
|
info = json.loads(result.stdout)
|
|
if 'backup' not in str(info.get('content', '')).split(','):
|
|
raise ValueError(f"{translate('The storage does not accept backups:')} {storage}")
|
|
return info
|
|
|
|
|
|
def dump_dir(storage):
|
|
"""The directory vzdump writes to on a file storage, or None."""
|
|
info = _storage(storage)
|
|
path = info.get('path')
|
|
return Path(path) / 'dump' if path else None
|
|
|
|
|
|
def validate(storage):
|
|
_storage(storage)
|
|
|
|
|
|
def before_update(vmid, storage):
|
|
"""A storage that cannot receive the file gets its own backup first."""
|
|
if dump_dir(storage) is not None:
|
|
return None
|
|
subprocess.run(['vzdump', str(vmid), '--storage', storage, '--mode', 'snapshot',
|
|
'--compress', 'zstd', '--notes-template', 'ProxMenux OCI: before the image update'],
|
|
check=True)
|
|
return storage
|
|
|
|
|
|
def keep(archive, storage):
|
|
"""Move the update's own backup into the storage; returns where it went,
|
|
or None when the storage received its backup before the update."""
|
|
directory = dump_dir(storage)
|
|
if directory is None:
|
|
return None
|
|
archive = Path(archive)
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
target = directory / archive.name
|
|
shutil.move(str(archive), str(target))
|
|
target.chmod(0o644)
|
|
stem = archive.name.split('.tar')[0]
|
|
log = archive.with_name(stem + '.log')
|
|
if log.is_file():
|
|
shutil.move(str(log), str(directory / log.name))
|
|
(directory / (archive.name + '.notes')).write_text('ProxMenux OCI: before the image update\n')
|
|
return str(target)
|