mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-02 03:29:12 +00:00
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:
co-authored by
Claude Opus 5.5
parent
386d33df6e
commit
4437a671d2
@@ -13,7 +13,7 @@ import tempfile
|
||||
import oci_instances as instances
|
||||
import oci_instance_transaction as transaction
|
||||
from oci_installation_state import image_from_archive
|
||||
from oci_ui import translate, msg_info, msg_ok, msg_error, msg_info2
|
||||
from oci_ui import translate, msg_info, msg_ok, msg_warn, msg_error, msg_info2
|
||||
|
||||
|
||||
def repository(reference):
|
||||
@@ -78,9 +78,28 @@ def resolve_archive(desired, config, current=None, check=None):
|
||||
if archive.is_symlink():
|
||||
raise ValueError(translate('Unsafe OCI archive path'))
|
||||
verifier = Path(__file__).with_name('verify_oci_archive.py')
|
||||
cached = archive.exists()
|
||||
if not cached:
|
||||
|
||||
def intact(path):
|
||||
try:
|
||||
run_quiet([sys.executable, str(verifier), str(path)],
|
||||
translate('The image did not pass the integrity check'))
|
||||
except RuntimeError:
|
||||
return False
|
||||
return image_from_archive(str(path))['manifest_digest'] == digest
|
||||
|
||||
if archive.exists():
|
||||
msg_info(translate('Verifying the image integrity...'))
|
||||
if intact(archive):
|
||||
msg_ok(translate('Using the verified image from the cache'))
|
||||
return archive, digest
|
||||
msg_warn(translate('The cached image is damaged; it will be downloaded again.'))
|
||||
archive.unlink()
|
||||
# A download can come back complete yet damaged when the connection drops
|
||||
# and the transfer resumes; the integrity check catches it, and a second
|
||||
# download is what repairs it.
|
||||
for attempt in (1, 2):
|
||||
msg_info(transaction.fit(f"{translate('Downloading the image:')} {reference}"))
|
||||
transaction.log(f'download attempt {attempt}/2')
|
||||
fd, name = tempfile.mkstemp(prefix='.proxmenux-update-', suffix='.tar', dir=archive.parent)
|
||||
os.close(fd)
|
||||
partial = Path(name)
|
||||
@@ -90,22 +109,19 @@ def resolve_archive(desired, config, current=None, check=None):
|
||||
'oci-archive:' + str(partial)], translate('Could not download the image'))
|
||||
msg_ok(translate('Image downloaded'))
|
||||
msg_info(translate('Verifying the image integrity...'))
|
||||
run_quiet([sys.executable, str(verifier), str(partial)],
|
||||
translate('The image did not pass the integrity check'))
|
||||
if image_from_archive(str(partial))['manifest_digest'] != digest:
|
||||
raise ValueError(translate('The downloaded image does not match its manifest'))
|
||||
partial.chmod(0o644)
|
||||
os.replace(partial, archive)
|
||||
if intact(partial):
|
||||
partial.chmod(0o644)
|
||||
os.replace(partial, archive)
|
||||
msg_ok(translate('Image integrity verified'))
|
||||
return archive, digest
|
||||
except RuntimeError:
|
||||
if attempt == 2:
|
||||
raise
|
||||
finally:
|
||||
partial.unlink(missing_ok=True)
|
||||
else:
|
||||
msg_info(translate('Verifying the image integrity...'))
|
||||
run_quiet([sys.executable, str(verifier), str(archive)],
|
||||
translate('The image did not pass the integrity check'))
|
||||
if image_from_archive(str(archive))['manifest_digest'] != digest:
|
||||
raise ValueError(translate('The cached image does not match the current digest'))
|
||||
msg_ok(translate('Using the verified image from the cache') if cached else translate('Image integrity verified'))
|
||||
return archive, digest
|
||||
if attempt == 2:
|
||||
raise RuntimeError(translate('Could not obtain an intact image after two attempts'))
|
||||
msg_warn(translate('The image download did not complete correctly; downloading it again...'))
|
||||
|
||||
|
||||
def kept_settings(changes, deployment):
|
||||
@@ -121,7 +137,7 @@ def kept_settings(changes, deployment):
|
||||
return kept
|
||||
|
||||
|
||||
def update(vmid, acknowledge_external_data=False, proposal=None):
|
||||
def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=None):
|
||||
operation = 'recreate' if proposal is not None else 'update'
|
||||
msg_info(translate('Checking the container before the update...') if operation == 'update'
|
||||
else translate('Checking the container before recreating it...'))
|
||||
@@ -140,11 +156,22 @@ def update(vmid, acknowledge_external_data=False, proposal=None):
|
||||
if archive is None:
|
||||
msg_ok(translate('The image is already up to date; nothing was changed.'))
|
||||
return
|
||||
file_storage = None
|
||||
if keep_backup:
|
||||
import oci_keep_backup
|
||||
oci_keep_backup.validate(keep_backup)
|
||||
if oci_keep_backup.dump_dir(keep_backup) is None:
|
||||
msg_info(f"{translate('Creating a backup in')} {keep_backup}...")
|
||||
oci_keep_backup.before_update(vmid, keep_backup)
|
||||
msg_ok(f"{translate('Backup created in')} {keep_backup}")
|
||||
else:
|
||||
file_storage = keep_backup
|
||||
kept = kept_settings(changes, desired['deployment'])
|
||||
if kept:
|
||||
msg_info2(f"{translate('Keeping the settings changed in Proxmox:')} {', '.join(kept)}")
|
||||
transaction.apply(instances.ROOT, vmid, archive, operation, proposal=proposal,
|
||||
registry_digest=digest, acknowledge_external_data=acknowledge_external_data)
|
||||
registry_digest=digest, acknowledge_external_data=acknowledge_external_data,
|
||||
keep_backup=file_storage)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -152,12 +179,13 @@ def main():
|
||||
parser.add_argument('vmid', type=int)
|
||||
parser.add_argument('--acknowledge-external-data', action='store_true')
|
||||
parser.add_argument('--proposal', type=Path)
|
||||
parser.add_argument('--keep-backup', metavar='STORAGE')
|
||||
args = parser.parse_args()
|
||||
if os.geteuid() != 0:
|
||||
parser.error(translate('Root privileges are required'))
|
||||
try:
|
||||
proposal = json.loads(args.proposal.read_text()) if args.proposal else None
|
||||
update(args.vmid, args.acknowledge_external_data, proposal)
|
||||
update(args.vmid, args.acknowledge_external_data, proposal, args.keep_backup)
|
||||
return 0
|
||||
except BlockingIOError:
|
||||
msg_error(translate('Another OCI operation is using the registry. This operation was not started.'))
|
||||
|
||||
Reference in New Issue
Block a user