Files
ProxMenux/oci/src/proxmenux_oci/custom_mounts.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

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