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

36 lines
2.2 KiB
Python

"""Build separate update/recreate proposals without mutating live contracts."""
import copy
def propose(instance, operation, current_template=None, edited_deployment=None):
if operation not in ('update', 'recreate'):
raise ValueError('Operacion no soportada')
if instance.get('status') != 'installed':
raise ValueError('La instancia no esta completada')
if operation == 'update' and (current_template is not None or edited_deployment is not None):
raise ValueError('Actualizar no puede modificar la configuracion; usa Recrear')
candidate = copy.deepcopy(instance)
if operation == 'recreate':
if current_template is None or edited_deployment is None:
raise ValueError('Recrear requiere la plantilla actual y la configuracion confirmada')
if current_template['id'] != instance['template']['id']:
raise ValueError('No se puede sustituir silenciosamente la aplicacion')
if edited_deployment.get('vmid') != instance['vmid']:
raise ValueError('Recrear conserva la identidad y el VMID')
candidate['template'] = copy.deepcopy(current_template)
candidate['deployment'] = copy.deepcopy(edited_deployment)
old = {m['container_path']: m for m in instance['deployment'].get('mounts', [])}
new = {m['container_path']: m for m in candidate['deployment'].get('mounts', [])}
if len(new) != len(candidate['deployment'].get('mounts', [])):
raise ValueError('Rutas duplicadas')
changed_storage = [p for p in old.keys() & new.keys()
if (old[p].get('type'), old[p].get('source')) !=
(new[p].get('type'), new[p].get('source'))]
return {'operation': operation, 'installation_id': instance['installation_id'],
'candidate': candidate, 'requires_confirmation': True,
'mounts': {'reuse': sorted(old.keys() & new.keys() - set(changed_storage)),
'add': sorted(new.keys() - old.keys()),
'detach_without_delete': sorted(old.keys() - new.keys()),
'storage_change_requires_migration': sorted(changed_storage)},
'execution_enabled': False}