mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-06 13:36:42 +00:00
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>
89 lines
4.8 KiB
Python
89 lines
4.8 KiB
Python
"""Image-specific GPU contracts, applied after catalog overlays on regeneration."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from .i18n import translate
|
|
|
|
|
|
LSIO_DEVICE_INIT = {"boinc", "emby", "jellyfin", "plex", "tvheadend"}
|
|
|
|
|
|
def apply_gpu_contract(template: dict[str, Any]) -> None:
|
|
profile = template.get("proxmox", {}).get("installer_profile", {})
|
|
if profile.get('selkies'):
|
|
apply_selkies_contract(template)
|
|
groups = [profile, *profile.get("hardware_acceleration", {}).get("profiles", [])]
|
|
requests = [item for group in groups for key in ("device_requests", "optional_devices")
|
|
for item in group.get(key, [])]
|
|
gpu = [item for item in requests if item.get("kind") == "nvidia-runtime"
|
|
or str(item.get("host_path_default", "")).startswith(("/dev/dri", "/dev/kfd"))]
|
|
if not gpu:
|
|
return
|
|
image = template.get("container_contract", {}).get("image", {}).get("reference", "")
|
|
repository = image.split("@", 1)[0].split(":", 1)[0]
|
|
for item in gpu:
|
|
if str(item.get("host_path_default", "")).startswith("/dev/dri/renderD"):
|
|
item["container_path_strategy"] = "same-as-host"
|
|
profile["gpu_validation"] = {
|
|
"device_inventory": "host-sysfs-and-stat",
|
|
"application_acceleration": "requires-workload-test",
|
|
"tone_mapping": "not-implied-by-device-access",
|
|
}
|
|
if repository in {"lscr.io/linuxserver/" + app for app in LSIO_DEVICE_INIT} or profile.get('selkies'):
|
|
profile["device_permissions"] = {
|
|
"strategy": "linuxserver-native-init",
|
|
"service_user": "abc",
|
|
"environment": "ATTACHED_DEVICES_PERMS",
|
|
"paths": "all-resolved-selected-character-devices",
|
|
}
|
|
elif repository == "jlesage/handbrake":
|
|
for item in gpu:
|
|
item["append_host_device_gid_to_environment"] = "SUP_GROUP_IDS"
|
|
|
|
|
|
def apply_selkies_contract(template):
|
|
profile = template['proxmox']['installer_profile']
|
|
if not template['container_contract']['image']['reference'].startswith('lscr.io/linuxserver/'):
|
|
raise ValueError(translate('The Selkies profile requires a verified LinuxServer image'))
|
|
environment = template['container_contract']['environment']
|
|
if not any(e['name'] == 'LC_ALL' for e in environment):
|
|
environment.append({'name': 'LC_ALL', 'example': '', 'required': False,
|
|
'sensitive': False, 'source': 'upstream-documentation',
|
|
'prompt': 'Language/locale (e.g. es_ES.UTF-8; translation of every application is not guaranteed)'})
|
|
mounts = profile.setdefault('tmpfs_mounts', [])
|
|
if not any(m['container_path'] == '/run/nginx' for m in mounts):
|
|
mounts.append({'id': 'nginx-runtime', 'container_path': '/run/nginx',
|
|
'default_size_mb': 1, 'minimum_size_mb': 1, 'prompt_size': False,
|
|
'mount_options': ['rw', 'nosuid', 'nodev', 'mode=0755']})
|
|
if profile.get('hardware_acceleration') or profile.get('device_requests'):
|
|
return
|
|
profile['optional_devices'] = [d for d in profile.get('optional_devices', [])
|
|
if not str(d.get('host_path_default', '')).startswith('/dev/dri')]
|
|
profile['hardware_acceleration'] = {
|
|
'prompt': 'Selkies desktop and streaming acceleration', 'default': 'none',
|
|
'profiles': [
|
|
{'id': 'none', 'label': 'No GPU (CPU)', 'device_requests': [],
|
|
'environment': [{'name': 'AUTO_GPU', 'value': 'false'}]},
|
|
{'id': 'vaapi', 'label': 'Intel/AMD (streaming rendering and encoding)',
|
|
'device_requests': [{'id': 'selkies-render', 'kind': 'character-device',
|
|
'path_prompt': 'Intel/AMD render node', 'host_path_default': '/dev/dri/renderD128',
|
|
'container_path_strategy': 'same-as-host', 'mode': '0660',
|
|
'deny_write': False, 'gid_strategy': 'host-device-gid',
|
|
'drm_vendor_ids': ['0x8086', '0x1002']}],
|
|
'environment': [{'name': 'PIXELFLUX_WAYLAND', 'value': 'true'},
|
|
{'name': 'AUTO_GPU', 'value': 'false'}],
|
|
'environment_from_devices': {'DRINODE': ['selkies-render'],
|
|
'DRI_NODE': ['selkies-render'],
|
|
'ATTACHED_DEVICES_PERMS': ['selkies-render']}}
|
|
]}
|
|
|
|
|
|
def apply_profile_image(template, hardware_profile):
|
|
"""Resolve an upstream image channel declared by the selected GPU profile."""
|
|
import copy
|
|
profiles = template.get('proxmox', {}).get('installer_profile', {}).get('hardware_acceleration', {}).get('profiles', [])
|
|
selected = next((profile for profile in profiles if profile['id'] == hardware_profile), {})
|
|
if selected.get('image'):
|
|
template['container_contract']['image'] = copy.deepcopy(selected['image'])
|