Files
ProxMenux/oci/remote/oci_accelerators.py
T
MacRimiandClaude Opus 5 bcabcb618c feat(oci): run official container images as native LXC containers
Adds the OCI manager: an engine that turns a Docker Compose file into an
LXC definition, a catalog of 365 applications drawn from LinuxServer.io
and other container image sources, and a per-instance registry recording
what each container was built from. Reachable from the main menu.

Catalog text is translated like every other string in the project: the
taglines go through translate() and land in lang/*.json, so the entries
read in all eight languages instead of only English.

Translation cache builder:
- a failed translation leaves the key absent rather than writing English,
  which previously made the string count as translated forever
- a result identical to a 3+ word source is rejected, catching a provider
  that silently returns the text it was given
- strings that are nothing but glossary terms keep their source spelling
  instead of being discarded as failures
- no backoff between attempts when the provider is deterministic
- application names are protected so "HAOS One" survives translation
- argos joins the provider list, and the workflow reads the OCI sources

Audit & Report:
- findings that moved in the wrong direction between runs are reported
  alongside the ones that improved
- an accepted risk can carry a review date and is flagged when it falls due
- backup checks explain in plain language what they looked at and what to
  do next

Monitor:
- disks can be excluded from periodic reads, and an idle disk says so
  instead of showing a stale temperature
- per-disk identity survives a controller or enclosure change
- scheduled Borg backups resolve their SSH key from the repository entry
- PVE upgrades log the package list and the resulting dpkg changes

The web build no longer copies scripts/ into public/: the documentation
links to GitHub, so nothing read that folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 18:24:59 +02:00

125 lines
4.8 KiB
Python

"""Preservation profiles for native DRM devices and NVIDIA Toolkit runtimes."""
from __future__ import annotations
import re
import oci_runtime_settings as runtime_settings
import oci_nvidia_dynamic as dynamic
import oci_gpu_devices as drm
import oci_nvidia_runtime as nvidia
from oci_ui import translate
def dynamic_mode(deployment):
return any(d.get('kind') == 'nvidia-runtime' and d.get('runtime_mode') == 'dynamic'
for d in deployment.get('devices', []))
def check_dynamic(config, value, deployment):
hooks = [line.split(': ', 1)[1] for line in config.decode().splitlines()
if line.startswith('lxc.hook.mount: ')]
if len(hooks) != 1:
raise ValueError(translate('The dynamic NVIDIA hook is missing or duplicated'))
match = re.fullmatch(r'/usr/local/lib/proxmenux/oci/nvidia-mount-([a-f0-9]{64})\.sh', hooks[0])
if not match:
raise ValueError(translate('The NVIDIA hook path does not belong to the installer'))
capabilities = next((e['value'] for e in reversed(deployment.get('environment', []))
if e['name'] == 'NVIDIA_DRIVER_CAPABILITIES'), 'compute,utility,video')
return dynamic.validate(config, value, value, hooks[0], match[1], capabilities)
def verify_baseline(expected, deployment):
if not dynamic_mode(deployment):
verify(expected)
return
drm.verify({p: v for p, v in expected.items() if p != nvidia.KEY})
if dynamic.gpu_identity(expected[nvidia.KEY]) != dynamic.gpu_identity(nvidia.snapshot()):
raise ValueError(translate('The selected GPU changed'))
def drm_plan(deployment):
return dict(deployment, devices=[d for d in deployment.get('devices', []) if d.get('kind') != 'nvidia-runtime'])
def planned(deployment):
result = drm.planned(drm_plan(deployment))
if nvidia.enabled(deployment):
value = nvidia.snapshot()
if set(result) & set(value['devices']):
raise ValueError(translate('Duplicated NVIDIA devices'))
result[nvidia.KEY] = value
return result
def verify(expected):
drm.verify({p: v for p, v in expected.items() if p != nvidia.KEY})
if nvidia.KEY in expected:
nvidia.verify(expected[nvidia.KEY])
def check(config, deployment):
config = runtime_settings.filter_config(config, deployment)
expected = planned(deployment)
value = expected.get(nvidia.KEY)
if value:
nvidia.check_devices(config, value)
if dynamic_mode(deployment):
check_dynamic(config, value, deployment)
else:
nvidia.check_mounts(config, value)
filtered = []
for line in config.splitlines(keepends=True):
if re.match(rb'dev[0-9]+: ', line):
fields = dict(part.split('=', 1) for part in line.decode().strip().split(': ', 1)[1].split(','))
if fields.get('path') in value['devices']:
continue
filtered.append(line)
filtered = b''.join(filtered)
drm.check(filtered, drm_plan(deployment))
else:
if nvidia.mount_lines(config):
raise ValueError(translate('LXC entries outside the selected acceleration profile'))
drm.check(config, deployment)
return expected
def capture(config):
result = drm.capture(config)
if any(isinstance(p, str) and p.startswith('/dev/nvidia') for p in drm.actual_devices(config)):
result[nvidia.KEY] = nvidia.snapshot()
return result
def verify_observation(expected, observed):
if observed.get('gpu_devices', {}) != expected:
raise ValueError(translate('The acceleration evidence differs from the verified inventory'))
verify(expected)
def validate_runtime(vmid, deployment):
if nvidia.enabled(deployment):
value = nvidia.snapshot()
if dynamic_mode(deployment):
rows = nvidia.command('pct', 'exec', str(vmid), '--', 'nvidia-smi', nvidia.QUERY, '--format=csv,noheader')
if sorted(line.strip() for line in rows.splitlines() if line.strip()) != value['gpus']:
raise ValueError(translate('NVML does not match the current host driver'))
else:
nvidia.validate_runtime(vmid, value)
def check_recovery_entries(config, state):
runtime_settings.check_recovery(config, state)
for key in ('record', 'candidate_contract'):
config = runtime_settings.filter_config(config, state.get(key, {}).get('deployment', {}))
entries = nvidia.mount_lines(config)
if not entries:
return
values = [state.get(key, {}).get(nvidia.KEY) for key in ('original_gpu_devices', 'desired_gpu_devices')]
for value in values:
if value:
try:
nvidia.check_mounts(config, value, complete=False)
return
except ValueError:
continue
raise ValueError(translate('LXC entries outside the NVIDIA inventory of the journal'))