mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-08 14:36:38 +00:00
- Immich asks what runs its video and its recognition in one menu, in both modes, and gives the GPU to the server and to Machine learning; AMD uses ROCm - Frigate, Ollama, llama.cpp, Faster Whisper and Piper take the image built for the chosen GPU - The acceleration menu offers only what the host can run - An update or a recreation sends one notification with its result instead of the stop, backup and start of each container - A private bridge with nothing connected is not reported as down - Secondary containers of a stack appear in the App tab with their version and logo; Secure Gateway shows the same update state in both views - A mistyped value in the wizard asks the same question again
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""What the Monitor is told about an update or a recreation.
|
|
|
|
While the operation runs, every container it touches is stopped, backed up
|
|
and started again. Those steps belong to the operation, so the containers are
|
|
marked for the Monitor to keep their stop, start and backup notices to itself,
|
|
and one notification with the result is sent when it ends.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import json
|
|
from pathlib import Path
|
|
import socket
|
|
import ssl
|
|
import time
|
|
import urllib.request
|
|
|
|
MARKERS = Path('/run/proxmenux/oci-operations')
|
|
ENDPOINTS = ('http://127.0.0.1:8008/api/internal/oci-event', 'https://127.0.0.1:8008/api/internal/oci-event')
|
|
|
|
|
|
def _write(vmid, data):
|
|
try:
|
|
MARKERS.mkdir(parents=True, exist_ok=True)
|
|
path = MARKERS / str(int(vmid))
|
|
temporary = path.with_suffix('.tmp')
|
|
temporary.write_text(json.dumps(data))
|
|
temporary.replace(path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def begin(vmids):
|
|
started = time.time()
|
|
for vmid in vmids:
|
|
_write(vmid, {'started': started, 'ended': None})
|
|
|
|
|
|
def end(vmids):
|
|
# The notices of the last start can arrive after the operation returned;
|
|
# the Monitor keeps the mark for a short while after `ended`.
|
|
ended = time.time()
|
|
for vmid in vmids:
|
|
_write(vmid, {'started': ended, 'ended': ended})
|
|
|
|
|
|
def notify(event, data):
|
|
"""Best effort: the operation never depends on the Monitor answering."""
|
|
payload = json.dumps({'event': event, 'hostname': socket.gethostname(), **data}).encode()
|
|
context = ssl.create_default_context()
|
|
context.check_hostname = False
|
|
context.verify_mode = ssl.CERT_NONE
|
|
for url in ENDPOINTS:
|
|
request = urllib.request.Request(url, data=payload, headers={'Content-Type': 'application/json'})
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=5, context=context if url.startswith('https') else None):
|
|
return True
|
|
except (OSError, ValueError):
|
|
continue
|
|
return False
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def operation(vmids, kind, application, primary=None):
|
|
"""Mark the containers for the length of an update or a recreation and
|
|
report how it ended. `kind` is 'update' or 'recreate'."""
|
|
vmids = [int(vmid) for vmid in vmids]
|
|
data = {'app_name': str(application), 'vmid': int(primary if primary is not None else vmids[0]),
|
|
'containers': ', '.join(f'CT {vmid}' for vmid in vmids)}
|
|
begin(vmids)
|
|
try:
|
|
yield
|
|
except BaseException as error:
|
|
end(vmids)
|
|
notify(f'oci_{kind}_failed', {**data, 'reason': str(error) or type(error).__name__})
|
|
raise
|
|
end(vmids)
|
|
notify(f'oci_{kind}_completed', data)
|