mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-08 22:46:41 +00:00
- Immich asks what runs its video and its recognition in one menu, in both modes, and gives the GPU to the server and to Machine learning; AMD uses ROCm - Frigate, Ollama, llama.cpp, Faster Whisper and Piper take the image built for the chosen GPU - The acceleration menu offers only what the host can run - An update or a recreation sends one notification with its result instead of the stop, backup and start of each container - A private bridge with nothing connected is not reported as down - Secondary containers of a stack appear in the App tab with their version and logo; Secure Gateway shows the same update state in both views - A mistyped value in the wizard asks the same question again
124 lines
6.6 KiB
Python
124 lines
6.6 KiB
Python
"""A secondary container of an OCI stack is registered with the version it
|
|
runs and no version tracking; its application is updated with the stack."""
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
import lxc_apps
|
|
|
|
DIGEST = 'sha256:' + 'ab' * 32
|
|
|
|
|
|
class StackMemberRegistrationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(tmp.cleanup)
|
|
self.root = Path(tmp.name)
|
|
(self.root / 'catalog').mkdir()
|
|
(self.root / 'catalog/index.json').write_text(json.dumps({'applications': []}))
|
|
patches = [patch.object(lxc_apps, '_OCI_INSTANCE_ROOT', str(self.root / 'instances')),
|
|
patch.object(lxc_apps, '_OCI_CATALOG_INDEX', str(self.root / 'catalog/index.json')),
|
|
patch.object(lxc_apps, '_oci_catalog_cache', None),
|
|
patch.object(lxc_apps, '_APPS_DIR', str(self.root / 'apps')),
|
|
patch.object(lxc_apps, '_OCI_DISMISSED_FILE', str(self.root / 'apps/.oci-dismissed.json'))]
|
|
for item in patches:
|
|
item.start()
|
|
self.addCleanup(item.stop)
|
|
|
|
def record(self, vmid, primary, reference):
|
|
folder = self.root / f'instances/{vmid}'
|
|
folder.mkdir(parents=True)
|
|
(folder / 'oci-compose.json').write_text(json.dumps({
|
|
'vmid': vmid, 'status': 'installed', 'installation_id': f'install-{vmid}',
|
|
'stack_member': {'name': 'database', 'primary_vmid': primary},
|
|
'stack': {'template': {'id': 'stack-nextcloud', 'catalog_ui': {'title': 'Nextcloud'},
|
|
'first_run': {'endpoints': [{'port': 80, 'scheme': 'http', 'path': '/'}]}}},
|
|
'observed': {'image': {'manifest_digest': DIGEST, 'architecture': 'amd64'}},
|
|
'template': {'id': f'stack-nextcloud-{vmid}',
|
|
'container_contract': {'image': {'reference': reference},
|
|
'ports': [{'container_port': 5432}]}}}))
|
|
|
|
def test_the_database_of_a_stack_is_registered_without_a_web_port(self):
|
|
self.record(131, 129, 'docker.io/library/postgres:16-alpine')
|
|
with patch.object(lxc_apps, 'add_app', return_value=(True, {})) as add:
|
|
self.assertTrue(lxc_apps.ensure_oci_registration(131))
|
|
payload = add.call_args.args[1]
|
|
self.assertEqual(payload['name'], 'Postgres')
|
|
self.assertEqual(payload['installed_via'], 'oci_image')
|
|
self.assertEqual(payload['ports'], [])
|
|
|
|
def test_a_secondary_container_is_told_apart_from_the_main_one(self):
|
|
self.assertTrue(lxc_apps._oci_secondary_member({'vmid': 131, 'stack_member': {'primary_vmid': 129}}))
|
|
self.assertFalse(lxc_apps._oci_secondary_member({'vmid': 129, 'stack_member': {'primary_vmid': 129}}))
|
|
self.assertFalse(lxc_apps._oci_secondary_member({'vmid': 120}))
|
|
self.assertFalse(lxc_apps._oci_secondary_member(None))
|
|
|
|
def test_the_registry_is_not_asked_for_a_secondary_container(self):
|
|
self.record(131, 129, 'docker.io/library/postgres:16-alpine')
|
|
|
|
class Engine:
|
|
@staticmethod
|
|
def resolve_candidate(reference, architecture):
|
|
assert '@' in reference, 'only the installed digest is read'
|
|
return {'version': '16.15', 'created': '2026-09-01T00:00:00Z', 'manifest_digest': DIGEST}
|
|
|
|
with patch.object(lxc_apps, '_oci_state_module', return_value=Engine):
|
|
result = lxc_apps._oci_image_versions(
|
|
131, with_latest=not lxc_apps._oci_secondary_member(lxc_apps._read_oci_record(131)))
|
|
self.assertEqual(result['installed_version'], '16.15')
|
|
self.assertNotIn('update_available', result)
|
|
self.assertNotIn('latest_version', result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|
|
|
|
class StackLogoTests(StackMemberRegistrationTests):
|
|
ICON = 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/immich.webp'
|
|
|
|
def stack(self):
|
|
(self.root / 'catalog/index.json').write_text(json.dumps({'applications': [
|
|
{'id': 'immich', 'template_id': 'image-immich', 'template': 'apps/immich.json', 'icon': self.ICON}]}))
|
|
members = {115: ('server', 'ghcr.io/immich-app/immich-server:release'),
|
|
116: ('machine-learning', 'ghcr.io/immich-app/immich-machine-learning:release'),
|
|
117: ('database', 'ghcr.io/immich-app/postgres:14-vectorchord0.4.3'),
|
|
118: ('valkey', 'docker.io/valkey/valkey:9')}
|
|
for vmid, (role, reference) in members.items():
|
|
folder = self.root / f'instances/{vmid}'
|
|
folder.mkdir(parents=True)
|
|
record = {'vmid': vmid, 'status': 'installed', 'installation_id': f'install-{vmid}',
|
|
'stack_member': {'name': role, 'primary_vmid': 115},
|
|
'observed': {'image': {'manifest_digest': DIGEST, 'architecture': 'amd64'}},
|
|
'template': {'id': f'image-immich-{role}',
|
|
'container_contract': {'image': {'reference': reference}, 'ports': []}}}
|
|
if vmid == 115:
|
|
record['stack'] = {'template': {'id': 'image-immich', 'catalog_ui': {'title': 'Immich', 'icon': None}}}
|
|
(folder / 'oci-compose.json').write_text(json.dumps(record))
|
|
|
|
def test_the_main_container_and_machine_learning_wear_the_application_logo(self):
|
|
self.stack()
|
|
self.assertEqual(lxc_apps._oci_instance_meta(115)['logo'], self.ICON)
|
|
self.assertEqual(lxc_apps._oci_instance_meta(116)['logo'], self.ICON)
|
|
|
|
def test_the_database_and_the_cache_wear_their_own(self):
|
|
self.stack()
|
|
self.assertTrue(lxc_apps._oci_instance_meta(117)['logo'].endswith('/postgresql.webp'))
|
|
self.assertTrue(lxc_apps._oci_instance_meta(118)['logo'].endswith('/valkey.webp'))
|
|
|
|
def test_an_application_registered_without_logo_takes_it_later(self):
|
|
self.stack()
|
|
sidecar = {'vmid': 115, 'apps': [{'id': 'a', 'name': 'Immich', 'installed_via': 'oci_image', 'logo_url': ''},
|
|
{'id': 'b', 'name': 'Other', 'installed_via': '', 'logo_url': ''}]}
|
|
written = {}
|
|
with patch.object(lxc_apps, '_read_sidecar', return_value=sidecar), \
|
|
patch.object(lxc_apps, '_write_sidecar', side_effect=lambda vmid, data: written.update(data)):
|
|
self.assertFalse(lxc_apps.ensure_oci_registration(115))
|
|
self.assertEqual(written['apps'][0]['logo_url'], self.ICON)
|
|
self.assertEqual(written['apps'][1]['logo_url'], '')
|