mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-04 04:26:40 +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>
64 lines
2.9 KiB
Python
64 lines
2.9 KiB
Python
"""Optional user mounts, separate from the image's required persistence."""
|
|
from pathlib import PurePosixPath
|
|
|
|
from .i18n import translate
|
|
from .ui import UserCancelled
|
|
|
|
|
|
def valid_path(value):
|
|
if (not value.startswith('/') or value == '/' or
|
|
any(c.isspace() or c in ',\x00' for c in value) or
|
|
any(part in ('.', '..') for part in value.split('/'))):
|
|
raise ValueError(translate('Invalid absolute path; avoid spaces, commas and relative segments'))
|
|
value = str(PurePosixPath(value))
|
|
if value.startswith('//'):
|
|
raise ValueError(translate('Invalid path'))
|
|
return value
|
|
|
|
|
|
def overlaps(a, b):
|
|
return a == b or a.startswith(b.rstrip('/') + '/') or b.startswith(a.rstrip('/') + '/')
|
|
|
|
|
|
def validate_mount(mount, existing):
|
|
target = valid_path(mount['container_path'])
|
|
protected = ('/bin', '/sbin', '/etc', '/usr', '/lib', '/lib64', '/proc', '/sys', '/dev', '/run')
|
|
if any(overlaps(target, p) for p in protected):
|
|
raise ValueError(translate('The custom path cannot hide system directories'))
|
|
if any(overlaps(target, valid_path(m['container_path'])) for m in existing):
|
|
raise ValueError(translate('The custom path overlaps another mount'))
|
|
if mount['type'] == 'managed-volume':
|
|
if int(mount['size_gb']) < 1 or not mount.get('backup'):
|
|
raise ValueError(translate('Invalid internal volume'))
|
|
elif mount['type'] == 'host-bind':
|
|
valid_path(mount['source'])
|
|
if mount.get('backup'):
|
|
raise ValueError(translate('Bind mounts are not included in vzdump'))
|
|
else:
|
|
raise ValueError(translate('Invalid mount type'))
|
|
return target
|
|
|
|
|
|
def ask_custom_mounts(ui, mounts, storage):
|
|
result = list(mounts)
|
|
while ui.confirm(translate('Add an extra custom path'), False):
|
|
target = ui.ask(translate('Path inside the container (e.g. /media-extra)'))
|
|
mode = ui.choose(translate('Data location'), [
|
|
('managed-volume', translate('Container volume (included in backups)')),
|
|
('host-bind', translate('Host directory (not included in Proxmox backups)')),
|
|
], 'managed-volume')
|
|
if mode is None:
|
|
raise UserCancelled(translate('Custom path cancelled'))
|
|
mount = {'type': mode, 'container_path': target, 'custom': True,
|
|
'source': storage, 'size_gb': None, 'backup': mode == 'managed-volume',
|
|
'read_only': ui.confirm(translate('Mount read-only'), False),
|
|
'create_if_missing': mode == 'host-bind'}
|
|
if mode == 'managed-volume':
|
|
mount['source'] = ui.ask(translate('Proxmox storage for the volume'), storage)
|
|
mount['size_gb'] = int(ui.ask(translate('Volume size in GB'), '8'))
|
|
else:
|
|
mount['source'] = ui.ask(translate('Host directory (created if it does not exist)'), '/mnt/oci-shared/custom')
|
|
mount['container_path'] = validate_mount(mount, result)
|
|
result.append(mount)
|
|
return result
|