fix: more messages say only what is certain

- Host restore notification: reports its tasks and no longer calls the node fully ready.
- Watchdog notification: covers an application that did not start as well as one that stopped again.
- OCI: Modify summary, Immich recognition preview, private network kept by another guest, empty USB list.
- gzip diagnostic no longer says that nothing changed.

Source questions from the Italian review by @f3rs3n.
This commit is contained in:
MacRimi
2026-10-07 07:35:10 +02:00
parent fc14ff5c33
commit f0d7d4f8f7
24 changed files with 105 additions and 77 deletions
@@ -76,22 +76,28 @@ class BackupSplitTests(unittest.TestCase):
self.assertEqual(result['title'], 'alias: Backup outcome unconfirmed')
self.assertEqual(result['body'], 'The backup outcome is not confirmed.')
def test_restore_keeps_original_ready_line_and_success_icon_with_warnings(self):
def test_restore_reports_its_tasks_without_calling_the_node_ready_and_keeps_the_success_icon(self):
import json
from pathlib import Path
from notification_final_fixture import restore_event
ready = {'en': 'The node is now fully ready to use.', 'de': 'Der Knoten ist nun vollständig einsatzbereit.', 'es': 'El nodo está listo para usarse.', 'fr': 'Le nœud est maintenant entièrement prêt à être utilisé.', 'it': "Il nodo è ora completamente pronto per l'uso.", 'pt': 'O nó agora está totalmente pronto para uso.', 'sk': 'Uzol je teraz úplne pripravený na použitie.', 'sv': 'Noden är nu helt redo att användas.'}
messages = Path(__file__).resolve().parents[3] / 'AppImage/messages'
first = {language: json.loads((messages / language / 'common.json').read_text())
['runtime']['notifications']['templates']['system_restore_completed']['body'].split('\n')[0]
for language in LANGUAGES}
for warning in ('', 'missing module zfs'):
event = restore_event(warning)
for language in LANGUAGES:
result = deliver(event['event_type'], event['data'], event['severity'], language)
self.assertTrue(result['title'].startswith('✅ '))
self.assertIn(ready[language], result['body'])
self.assertIn(ready[language], result['text'])
self.assertIn(first[language], result['body'])
self.assertIn(first[language], result['text'])
self.assertNotIn('fully ready', result['body'])
self.assertIn('2m', result['text'])
if warning:
self.assertIn(warning, result['text'])
quiet = deliver(event['event_type'], event['data'], event['severity'], language, quiet=True)
self.assertIn('✅', quiet['body'])
self.assertIn(' ' + ready[language], quiet['body'])
self.assertIn(first[language], quiet['body'])
self.assertIn('white-space:pre-wrap;', quiet['html'])
def test_spanish_outcome_and_restore_titles_are_capitalized_and_failure_is_exact(self):
@@ -15,7 +15,7 @@ from notification_fixture import templates as actual_templates
ROOT = Path(__file__).resolve().parents[3]
SCRIPTS = ROOT / 'AppImage/scripts'
CATALOG = ROOT / 'AppImage/messages/en/common.json'
EXPECTED = {'system_restore_completed': {'body': 'Post-restore tasks completed in background.\n\nGuests applied: {guests}\nBind-mount stubs: {stubs}\nStale node dirs removed: {stale_nodes}\nComponents reinstalled: {components}\nDuration: {duration}\n{warnings_block}\nThe node is now fully ready to use.'}}
EXPECTED = {'system_restore_completed': {'body': 'Post-restore tasks completed in background.\n\nGuest configurations copied: {guests}\nBind-mount stub directories created: {stubs}\nStale node dirs removed: {stale_nodes}\nComponents reinstalled: {components}\nDuration: {duration}\n{warnings_block}'}}
def extract(path, name, owner=None, namespace=None):
@@ -72,7 +72,7 @@ class OutcomeWording(unittest.TestCase):
result = module.render_template('system_restore_completed', {
'hostname':'node-a','guests':3,'stubs':0,'stale_nodes':0,
'components':1,'duration':'2m','warnings_block':''}, 'es')
self.assertIn('Guests aplicados: 3', result['body'])
self.assertIn('Configuración de guests copiada: 3', result['body'])
self.assertNotIn('invitados', result['body'].lower())
def test_settings_labels_stay_at_upstream_values_in_all_locales(self):
@@ -345,7 +345,7 @@ class OutcomeWording(unittest.TestCase):
restore = module.render_template('system_restore_completed', {'hostname':'node', 'guests':4,
'stubs':1,'stale_nodes':2,'components':1,'duration':'2m','warnings_block':'Missing module'},lang)
self.assertIn('Missing module',restore['body'])
if lang == 'en': self.assertIn('fully ready',restore['body'].lower())
if lang == 'en': self.assertNotIn('fully ready',restore['body'].lower())
@@ -366,7 +366,7 @@ class OutcomeWording(unittest.TestCase):
self.assertEqual(event['severity'], 'WARNING' if warnings else 'INFO')
result = self.render(event['event_type'], event['data'], 'en')
self.assertIn('Post-restore tasks completed', result['body'])
self.assertIn('fully ready', result['body'])
self.assertNotIn('fully ready', result['body'])
if warnings: self.assertIn('missing modules', result['body'])
@@ -107,7 +107,7 @@ class PVE92Tests(unittest.TestCase):
with self.subTest(event=event, field=field):
value = slovak['templates'][event][field]
result = templates.render_template(event, data, 'sk')
self.assertEqual(result[field], value.format(**data))
self.assertEqual(result[field].strip(), value.format(**data).strip())
self.assertNotIn(value, (SCRIPTS / 'notification_templates.py').read_text())
# Independently updated and absent leaves use the usual provider.
import copy
@@ -49,3 +49,23 @@ class OutcomePromises(TestCase):
for key in ('deleteAttached', 'storageAttached'):
self.assertEqual(texts[key].count('{ids}'), 1, f'{locale} {key}')
self.assertEqual(texts['disableBody'].count('. '), 1, f'{locale} disableBody')
def test_the_host_restore_notice_reports_tasks_and_does_not_call_the_node_ready(self):
for locale in LOCALES:
body = json.loads((ROOT / f'AppImage/messages/{locale}/common.json').read_text())['runtime']['notifications']['templates']['system_restore_completed']['body']
self.assertEqual(body.split('\n')[-1], '{warnings_block}', locale)
self.assertEqual(body.count('\n'), 7, locale)
def test_the_watchdog_failure_covers_an_application_that_did_not_start(self):
english = json.loads((ROOT / 'AppImage/messages/en/common.json').read_text())['runtime']['notifications']['templates']['oci_watchdog_failed']
self.assertNotIn('keeps stopping', english['title'] + english['body'] + english['label'])
self.assertIn('did not start', english['body'])
def test_other_wording_that_promised_too_much(self):
texts = oci_menu_texts()
self.assertIn('Private network kept, because other guests still use it:', texts)
recreation = (ROOT / 'oci/src/proxmenux_oci/stack_recreation.py').read_text()
self.assertNotIn('if anything fails', recreation)
self.assertIn('a stopped one is left stopped', recreation)
for script in ('scripts/post_install/customizable_post_install.sh', 'scripts/post_install/uninstall-tools.sh'):
self.assertNotIn('leaving it unchanged', (ROOT / script).read_text(), script)
+5 -5
View File
@@ -2011,7 +2011,7 @@
"oci_modify_completed": "OCI-Anwendung geändert",
"oci_modify_failed": "Änderung der OCI-Anwendung fehlgeschlagen",
"oci_watchdog_restarted": "OCI-Anwendung vom Watchdog neu gestartet",
"oci_watchdog_failed": "OCI-Anwendung stoppt immer wieder",
"oci_watchdog_failed": "OCI-Anwendung vom Watchdog gestoppt belassen",
"app_update_available": "App-Update verfügbar",
"lxc_update_applied": "LXC Update angewendet",
"docker_stack_update_available": "Docker Updates verfügbar"
@@ -6451,9 +6451,9 @@
"label": "OCI-Anwendung vom Watchdog neu gestartet"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} stoppt immer wieder",
"body": "{app_name} stoppt immer wieder von selbst und wird vom Watchdog nicht mehr neu gestartet.\nContainer: {containers}\nStarten Sie die Anwendung von Hand, sobald die Ursache behoben ist; der Watchdog überwacht sie dann wieder.",
"label": "OCI-Anwendung stoppt immer wieder"
"title": "{hostname}: {app_name} konnte nicht am Laufen gehalten werden",
"body": "Der Watchdog konnte {app_name} nicht am Laufen halten: Die Anwendung stoppte erneut oder startete nach mehreren Versuchen nicht und bleibt gestoppt.\nContainer: {containers}\nStarten Sie die Anwendung von Hand, sobald die Ursache behoben ist; der Watchdog überwacht sie dann wieder.",
"label": "OCI-Anwendung vom Watchdog gestoppt belassen"
},
"app_update_available": {
"title": "{hostname}: {app_name} Update verfügbar auf CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname}: Host-Wiederherstellung abgeschlossen",
"body": "Aufgaben nach der Wiederherstellung im Hintergrund ausgeführt.\n\nGäste haben sich beworben: {guests}\nBind-Mount-Stubs: {stubs}\nVeraltete Knotenverzeichnisse entfernt: {stale_nodes}\nKomponenten neu installiert: {components}\nDauer: {duration}\n{warnings_block}\nDer Knoten ist nun vollständig einsatzbereit.",
"body": "Aufgaben nach der Wiederherstellung im Hintergrund ausgeführt.\n\nGastkonfigurationen kopiert: {guests}\nPlatzhalterverzeichnisse für Bind-Mounts erstellt: {stubs}\nVeraltete Knotenverzeichnisse entfernt: {stale_nodes}\nKomponenten neu installiert: {components}\nDauer: {duration}\n{warnings_block}",
"label": "Host-Wiederherstellung abgeschlossen"
},
"system_problem": {
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -1998,7 +1998,7 @@
"oci_modify_completed": "Aplicación OCI modificada",
"oci_modify_failed": "Modificación de aplicación OCI fallida",
"oci_watchdog_restarted": "Aplicación OCI reiniciada por la vigilancia",
"oci_watchdog_failed": "Aplicación OCI que se detiene una y otra vez",
"oci_watchdog_failed": "Aplicación OCI que la vigilancia deja parada",
"app_update_available": "Actualización de app disponible",
"ai_model_migrated": "Modelo de IA actualizado automáticamente",
"proxmenux_update": "Actualización de ProxMenux disponible",
@@ -6451,9 +6451,9 @@
"label": "Aplicación OCI reiniciada por la vigilancia"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} se detiene una y otra vez",
"body": "{app_name} sigue deteniéndose solo y la vigilancia ya no lo reinicia.\nContenedores: {containers}\nInícialo a mano cuando la causa esté resuelta; la vigilancia volverá a vigilarlo.",
"label": "Aplicación OCI que se detiene una y otra vez"
"title": "{hostname}: {app_name} no se ha podido mantener en marcha",
"body": "La vigilancia no ha podido mantener {app_name} en marcha: volvió a detenerse o no arrancó tras varios intentos, y se deja parado.\nContenedores: {containers}\nInícialo a mano cuando la causa esté resuelta; la vigilancia volverá a vigilarlo.",
"label": "Aplicación OCI que la vigilancia deja parada"
},
"app_update_available": {
"title": "{hostname}: actualización de {app_name} disponible en CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname}: Restauración del host finalizada",
"body": "Tareas posteriores a la restauración completadas en segundo plano.\n\nGuests aplicados: {guests}\nStubs de bind mount: {stubs}\nDirectorios de nodos obsoletos eliminados: {stale_nodes}\nComponentes reinstalados: {components}\nDuración: {duration}\n{warnings_block}\nEl nodo está listo para usarse.",
"body": "Tareas posteriores a la restauración completadas en segundo plano.\n\nConfiguración de guests copiada: {guests}\nDirectorios vacíos creados para bind mounts: {stubs}\nDirectorios de nodos obsoletos eliminados: {stale_nodes}\nComponentes reinstalados: {components}\nDuración: {duration}\n{warnings_block}",
"label": "Restauración del host completada"
},
"system_problem": {
+5 -5
View File
@@ -2011,7 +2011,7 @@
"oci_modify_completed": "Application OCI modifiée",
"oci_modify_failed": "Échec de la modification de l'application OCI",
"oci_watchdog_restarted": "Application OCI redémarrée par la surveillance",
"oci_watchdog_failed": "Application OCI qui s'arrête sans cesse",
"oci_watchdog_failed": "Application OCI laissée arrêtée par la surveillance",
"app_update_available": "mise à jour de l'application disponible",
"lxc_update_applied": "Mise à jour LXC appliquée",
"docker_stack_update_available": "Docker mises à jour disponibles"
@@ -6451,9 +6451,9 @@
"label": "Application OCI redémarrée par la surveillance"
},
"oci_watchdog_failed": {
"title": "{hostname} : {app_name} s'arrête sans cesse",
"body": "{app_name} continue de s'arrêter tout seul et la surveillance ne le redémarre plus.\nConteneurs : {containers}\nDémarrez-le à la main une fois la cause résolue ; la surveillance le reprend alors.",
"label": "Application OCI qui s'arrête sans cesse"
"title": "{hostname} : {app_name} n'a pas pu être maintenu en marche",
"body": "La surveillance n'a pas pu maintenir {app_name} en marche : il s'est arrêté de nouveau ou n'a pas démarré après plusieurs tentatives, et il reste arrêté.\nConteneurs : {containers}\nDémarrez-le à la main une fois la cause résolue ; la surveillance le reprend alors.",
"label": "Application OCI laissée arrêtée par la surveillance"
},
"app_update_available": {
"title": "{hostname} : mise à jour {app_name} disponible sur CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname} : restauration de l'hôte terminée",
"body": "Tâches post-restauration effectuées en arrière-plan.\n\nInvités postulés : {guests}\nTalons de montage liés : {stubs}\nRépertoires de nœuds obsolètes supprimés : {stale_nodes}\nComposants réinstallés : {components}\nDurée : {duration}\n{warnings_block}\nLe nœud est maintenant entièrement prêt à être utilisé.",
"body": "Tâches post-restauration effectuées en arrière-plan.\n\nConfigurations de VM et CT copiées : {guests}\nRépertoires vides créés pour les bind mounts : {stubs}\nRépertoires de nœuds obsolètes supprimés : {stale_nodes}\nComposants réinstallés : {components}\nDurée : {duration}\n{warnings_block}",
"label": "Restauration de l'hôte terminée"
},
"system_problem": {
+5 -5
View File
@@ -2011,7 +2011,7 @@
"oci_modify_completed": "App OCI modificata",
"oci_modify_failed": "Modifica app OCI non riuscita",
"oci_watchdog_restarted": "App OCI riavviata dal watchdog",
"oci_watchdog_failed": "App OCI che continua ad arrestarsi",
"oci_watchdog_failed": "App OCI lasciata ferma dal watchdog",
"app_update_available": "Aggiornamento app disponibile",
"lxc_update_applied": "Aggiornamento LXC applicato",
"docker_stack_update_available": "Aggiornamenti Docker disponibili"
@@ -6451,9 +6451,9 @@
"label": "App OCI riavviata dal watchdog"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} continua ad arrestarsi",
"body": "{app_name} continua ad arrestarsi da solo e il watchdog non lo riavvia più.\nContainer: {containers}\nAvvialo a mano una volta risolta la causa; il watchdog tornerà a sorvegliarlo.",
"label": "App OCI che continua ad arrestarsi"
"title": "{hostname}: non è stato possibile mantenere {app_name} in esecuzione",
"body": "Il watchdog non è riuscito a mantenere {app_name} in esecuzione: si è arrestato di nuovo o non si è avviato dopo diversi tentativi, e viene lasciato fermo.\nContainer: {containers}\nAvvialo a mano una volta risolta la causa; il watchdog tornerà a sorvegliarlo.",
"label": "App OCI lasciata ferma dal watchdog"
},
"app_update_available": {
"title": "{hostname}: aggiornamento {app_name} disponibile su CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname}: ripristino host terminato",
"body": "Attività post-ripristino completate in background.\n\nGli ospiti hanno presentato domanda: {guests}\nStub con montaggio tramite collegamento: {stubs}\nDirectory dei nodi obsolete rimosse: {stale_nodes}\nComponenti reinstallati: {components}\nDurata: {duration}\n{warnings_block}\nIl nodo è ora completamente pronto per l'uso.",
"body": "Attività post-ripristino completate in background.\n\nConfigurazioni dei guest copiate: {guests}\nDirectory stub per i bind mount create: {stubs}\nDirectory dei nodi obsolete rimosse: {stale_nodes}\nComponenti reinstallati: {components}\nDurata: {duration}\n{warnings_block}",
"label": "Ripristino host completato"
},
"system_problem": {
+5 -5
View File
@@ -2011,7 +2011,7 @@
"oci_modify_completed": "Aplicação OCI modificada",
"oci_modify_failed": "Falha na modificação da aplicação OCI",
"oci_watchdog_restarted": "Aplicação OCI reiniciada pela vigilância",
"oci_watchdog_failed": "Aplicação OCI que continua a parar",
"oci_watchdog_failed": "Aplicação OCI deixada parada pela vigilância",
"app_update_available": "atualização de aplicativo disponível",
"lxc_update_applied": "Atualização LXC aplicada",
"docker_stack_update_available": "Docker atualizações disponíveis"
@@ -6451,9 +6451,9 @@
"label": "Aplicação OCI reiniciada pela vigilância"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} continua a parar",
"body": "{app_name} continua a parar sozinho e a vigilância já não o reinicia.\nContêineres: {containers}\nInicie-o à mão quando a causa estiver resolvida; a vigilância volta então a vigiá-lo.",
"label": "Aplicação OCI que continua a parar"
"title": "{hostname}: não foi possível manter {app_name} em execução",
"body": "A vigilância não conseguiu manter {app_name} em execução: voltou a parar ou não arrancou após várias tentativas, e fica parado.\nContêineres: {containers}\nInicie-o à mão quando a causa estiver resolvida; a vigilância volta então a vigiá-lo.",
"label": "Aplicação OCI deixada parada pela vigilância"
},
"app_update_available": {
"title": "{hostname}: atualização {app_name} disponível no CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname}: restauração do host concluída",
"body": "Tarefas pós-restauração concluídas em segundo plano.\n\nConvidados inscritos: {guests}\nStubs de montagem de ligação: {stubs}\nDiretórios de nó obsoletos removidos: {stale_nodes}\nComponentes reinstalados: {components}\nDuração: {duration}\n{warnings_block}\nO nó agora está totalmente pronto para uso.",
"body": "Tarefas pós-restauração concluídas em segundo plano.\n\nConfigurações de VM e CT copiadas: {guests}\nDiretórios vazios criados para bind mounts: {stubs}\nDiretórios de nó obsoletos removidos: {stale_nodes}\nComponentes reinstalados: {components}\nDuração: {duration}\n{warnings_block}",
"label": "Restauração do host concluída"
},
"system_problem": {
+5 -5
View File
@@ -2012,7 +2012,7 @@
"oci_modify_completed": "OCI aplikácia upravená",
"oci_modify_failed": "Úprava OCI aplikácie zlyhala",
"oci_watchdog_restarted": "OCI aplikácia reštartovaná watchdogom",
"oci_watchdog_failed": "OCI aplikácia sa opakovane zastavuje",
"oci_watchdog_failed": "OCI aplikácia ponechaná zastavená watchdogom",
"app_update_available": "K dispozícii je aktualizácia aplikácie"
},
"ui": {
@@ -6450,9 +6450,9 @@
"label": "OCI aplikácia reštartovaná watchdogom"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} sa opakovane zastavuje",
"body": "Aplikácia {app_name} sa stále sama zastavuje a watchdog ju už nereštartuje.\nKontajnery: {containers}\nPo odstránení príčiny ju spustite ručne; watchdog ju potom bude znova sledovať.",
"label": "OCI aplikácia sa opakovane zastavuje"
"title": "{hostname}: {app_name} sa nepodarilo udržať v chode",
"body": "Watchdogu sa nepodarilo udržať aplikáciu {app_name} v chode: znova sa zastavila alebo sa po viacerých pokusoch nespustila a zostáva zastavená.\nKontajnery: {containers}\nPo odstránení príčiny ju spustite ručne; watchdog ju potom bude znova sledovať.",
"label": "OCI aplikácia ponechaná zastavená watchdogom"
},
"app_update_available": {
"title": "{hostname}: Pre {app_name} je na CT {vmid} dostupná aktualizácia",
@@ -6721,7 +6721,7 @@
},
"system_restore_completed": {
"title": "{hostname}: Obnova servera dokončená",
"body": "Úlohy po obnove boli dokončené na pozadí.\n\nPoužité VM a LXC: {guests}\nZástupné priečinky bind mountov: {stubs}\nOdstránené zastarané priečinky uzlov: {stale_nodes}\nPreinštalované súčasti: {components}\nTrvanie: {duration}\n{warnings_block}\nUzol je teraz úplne pripravený na použitie.",
"body": "Úlohy po obnove boli dokončené na pozadí.\n\nPoužité VM a LXC: {guests}\nZástupné priečinky bind mountov: {stubs}\nOdstránené zastarané priečinky uzlov: {stale_nodes}\nPreinštalované súčasti: {components}\nTrvanie: {duration}\n{warnings_block}",
"label": "Obnova servera bola dokončená"
},
"system_problem": {
+5 -5
View File
@@ -2011,7 +2011,7 @@
"oci_modify_completed": "OCI-applikation ändrad",
"oci_modify_failed": "Ändring av OCI-applikation misslyckades",
"oci_watchdog_restarted": "OCI-applikation omstartad av watchdog",
"oci_watchdog_failed": "OCI-applikation stannar om och om igen",
"oci_watchdog_failed": "OCI-applikation lämnad stoppad av watchdog",
"app_update_available": "Appuppdatering tillgänglig",
"lxc_update_applied": "LXC uppdatering tillämpad",
"docker_stack_update_available": "Docker uppdateringar tillgängliga"
@@ -6451,9 +6451,9 @@
"label": "OCI-applikation omstartad av watchdog"
},
"oci_watchdog_failed": {
"title": "{hostname}: {app_name} stannar om och om igen",
"body": "{app_name} fortsätter att stanna av sig själv och watchdog startar inte längre om den.\nContainrar: {containers}\nStarta den manuellt när orsaken är löst; watchdog övervakar den sedan igen.",
"label": "OCI-applikation stannar om och om igen"
"title": "{hostname}: {app_name} kunde inte hållas igång",
"body": "Watchdog kunde inte hålla {app_name} igång: den stannade igen eller startade inte efter flera försök, och den lämnas stoppad.\nContainrar: {containers}\nStarta den manuellt när orsaken är löst; watchdog övervakar den sedan igen.",
"label": "OCI-applikation lämnad stoppad av watchdog"
},
"app_update_available": {
"title": "{hostname}: {app_name} uppdatering tillgänglig på CT {vmid}",
@@ -6722,7 +6722,7 @@
},
"system_restore_completed": {
"title": "{hostname}: Värdåterställning avslutad",
"body": "Uppgifter efter återställning slutförda i bakgrunden.\n\nGäster ansökte: {guests}\nBind-monterade stubbar: {stubs}\nInaktuella nodkataloger har tagits bort: {stale_nodes}\nKomponenter installerade om: {components}\nVaraktighet: {duration}\n{warnings_block}\nNoden är nu helt redo att användas.",
"body": "Uppgifter efter återställning slutförda i bakgrunden.\n\nGästkonfigurationer kopierade: {guests}\nPlatshållarkataloger för bind-monteringar skapade: {stubs}\nInaktuella nodkataloger har tagits bort: {stale_nodes}\nKomponenter installerade om: {components}\nVaraktighet: {duration}\n{warnings_block}",
"label": "Värdåterställning slutförd"
},
"system_problem": {
+6 -7
View File
@@ -935,13 +935,13 @@ TEMPLATES = {
'default_enabled': True,
},
'oci_watchdog_failed': {
'title': '{hostname}: {app_name} keeps stopping',
'title': '{hostname}: {app_name} could not be kept running',
'body': (
'{app_name} keeps stopping on its own and the watchdog no longer restarts it.\n'
'The watchdog could not keep {app_name} running: it stopped again or did not start after several attempts, and it is left stopped.\n'
'Containers: {containers}\n'
'Start it by hand once the cause is solved; the watchdog then watches it again.'
),
'label': 'OCI application keeps stopping',
'label': 'OCI application left stopped by the watchdog',
'group': 'vm_ct',
'default_enabled': True,
},
@@ -1378,13 +1378,12 @@ TEMPLATES = {
'title': '{hostname}: Host restore finished',
'body': (
'Post-restore tasks completed in background.\n\n'
'Guests applied: {guests}\n'
'Bind-mount stubs: {stubs}\n'
'Guest configurations copied: {guests}\n'
'Bind-mount stub directories created: {stubs}\n'
'Stale node dirs removed: {stale_nodes}\n'
'Components reinstalled: {components}\n'
'Duration: {duration}\n'
'{warnings_block}\n'
'The node is now fully ready to use.'
'{warnings_block}'
),
'label': 'Host restore completed',
'group': 'services',
+6 -6
View File
@@ -101,6 +101,7 @@
"A relaxed AppArmor or seccomp profile is requested; this may be optional.": "Se solicita un perfil de AppArmor o seccomp relajado; puede ser opcional.",
"A reproducible native startup is missing": "Falta una startup nativa reproducible",
"A rootfs adaptation is stored in persistent storage": "Una adaptación de rootfs se almacena en almacenamiento persistente",
"A running container is restarted to apply the changes; a stopped one is left stopped. The data of the application, its database and the configuration of the other containers are not changed. A container volume removed here is deleted with the data in it.": "Un contenedor en marcha se reinicia para aplicar los cambios; uno parado se deja parado. Los datos de la aplicación, su base de datos y la configuración de los demás contenedores no cambian. Un volumen de contenedor eliminado aquí se borra con los datos que contiene.",
"A scheduled run cannot answer:": "Una ejecución programada no puede responder:",
"A self-hosted Bitwarden server": "Un servidor Bitwarden autoalojado",
"A self-hosted, goal-free habit tracking tool.": "Una herramienta autoalojada de seguimiento de hábitos, sin objetivos.",
@@ -4019,7 +4020,6 @@
"No Shares Found": "No se encontraron recursos compartidos",
"No Shares to Test": "Sin recursos compartidos que probar",
"No Storage Found": "No se encontró almacenamiento",
"No USB device was found on this host.": "No se ha encontrado ningún dispositivo USB en este host.",
"No USB drives detected. Enter the mountpoint path manually:": "No se detectaron unidades USB.Ingrese la ruta del punto de montaje manualmente:",
"No USB drives mounted by ProxMenux yet. Mount one first to use it as a target.": "Aún no hay unidades USB montadas por ProxMenux. Monte una primero para usarla como destino.",
"No UUP folder found.": "No se encontró ninguna carpeta UUP.",
@@ -4164,6 +4164,7 @@
"No scheduled backup jobs found.": "No se encontraron trabajos de backup programados.",
"No scripts found for:": "No se encontraron scripts para:",
"No security relaxation is required for the reviewed profile.": "El perfil revisado no necesita ninguna relajación de seguridad.",
"No selectable USB device was found. Type the host node by hand.": "No se ha encontrado ningún dispositivo USB que se pueda elegir. Escribe a mano el nodo del host.",
"No self-test history found for": "No se encontró ningún historial de autoevaluación para",
"No self-test log available for": "No hay ningún log de autoprueba disponible para",
"No server IP or hostname provided.": "No se proporcionó ninguna IP de servidor ni nombre de host.",
@@ -4660,7 +4661,7 @@
"Privacy-first, self-hosted PDF toolkit": "Kit de herramientas PDF autoalojado que da prioridad a la privacidad",
"Private installation record saved": "Registro de instalación privado guardado",
"Private network assigned automatically:": "Red privada asignada automáticamente:",
"Private network kept, because other containers still use it:": "Red privada conservada, porque otros contenedores la siguen usando:",
"Private network kept, because other guests still use it:": "Red privada conservada, porque otras VM o CT la siguen usando:",
"Private network of the application released:": "Red privada de la aplicación liberada:",
"Private network of the application that is released:": "Red privada de la aplicación que se libera:",
"Private network release attempted:": "Se ha intentado liberar la red privada:",
@@ -6164,7 +6165,6 @@
"The container identity or configuration changed": "La identidad o configuración del contenedor cambió",
"The container is currently stopped. Do you want to start it now to install the package?": "El contenedor se encuentra actualmente detenido. ¿Quieres iniciarlo ahora para instalar el paquete?",
"The container is not modified because a host directory is not available:": "El contenedor no se modifica porque un directorio host no está disponible:",
"The container is restarted to apply the changes. Its own data, the database and the other containers of the application are not touched.": "El contenedor se reinicia para aplicar los cambios. Sus datos propios, la base de datos y los demás contenedores de la aplicación no se tocan.",
"The container no longer exists:": "El contenedor ya no existe:",
"The container of a member was replaced; the assembly is not resumed": "El contenedor de un miembro fue reemplazado; la asamblea no se reanuda",
"The container runs on another node of the cluster; migrate it back to this node to remove it:": "El contenedor está en otro nodo del clúster; migra el contenedor de vuelta a este nodo para eliminarlo:",
@@ -6304,7 +6304,7 @@
"The local envelope is dropped and future backups do not upload anything. Uploaded envelopes already on PBS stay intact and remain recoverable with their original passphrase.": "El sobre local se descarta y los backups futuros no suben nada. Los sobres ya subidos a PBS permanecen intactos y recuperables con su frase de contraseña original.",
"The local storage does not accept snippets": "El almacenamiento local no admite snippets",
"The long test runs directly on the disk hardware.": "La prueba larga se ejecuta directamente en el hardware del disco.",
"The machine learning container is rebuilt with the image of the new choice. Its model cache, the library and the database are kept. The whole application is stopped and updated, as in an update; if anything fails, the previous containers and the previous choice are restored.": "El contenedor Machine learning se reconstruye con la imagen de la nueva opción. Se conservan su caché de modelos, la biblioteca y la base de datos. Toda la aplicación se detiene y se actualiza, como en una actualización; si algo falla, se restauran los contenedores y la opción anteriores.",
"The machine learning container is rebuilt with the image of the new choice. Its model cache, the library and the database are kept. The whole application is stopped and updated, as in an update. If the operation fails after a container was changed, the previous containers and the previous choice are restored. An operation that stops halfway is recovered from the OCI management menu.": "El contenedor Machine learning se reconstruye con la imagen de la nueva opción. Se conservan su caché de modelos, la biblioteca y la base de datos. Toda la aplicación se detiene y se actualiza, como en una actualización. Si la operación falla después de modificar un contenedor, se restauran los contenedores y la opción anteriores. Una operación que se queda a medias se recupera desde el menú de gestión OCI.",
"The main container of this application is not on this host. Restore it too.": "El contenedor principal de esta aplicación no está en este host. Restáuralo también.",
"The main member must stop first and start last": "El miembro principal debe parar primero y comenzar el último",
"The main member of the stack is missing": "Falta el miembro principal de la pila",
@@ -6375,7 +6375,7 @@
"The private bridge does not have the expected address:": "El puente privado no tiene la dirección prevista:",
"The private journal has an unsafe owner or permissions": "El diario privado tiene un propietario o permisos inseguros",
"The private network allocator was not found": "El aleator de la red privada no fue encontrado",
"The private network is still used by another container and is kept:": "Otro contenedor sigue usando la red privada, así que se conserva:",
"The private network is still used by another guest and is kept:": "Otra VM u otro CT sigue usando la red privada, así que se conserva:",
"The private network must be assigned automatically": "La red privada debe ser asignada automáticamente",
"The private network of the application is already in use on this host:": "La red privada de la aplicación ya está en uso en este host:",
"The privileged deployment does not include the required explicit consent": "El despliegue privilegiado no incluye el consentimiento explícito necesario",
@@ -7482,7 +7482,7 @@
"go2rtc WebUI": "go2rtc WebUI",
"google/gasket-driver cloned (fallback — will apply local patches).": "google/gasket-driver clonado (alternativa; se aplicarán parches locales).",
"gpg not found; trying apt-key fallback": "gpg no encontrado; se prueba la alternativa apt-key",
"gzip could not be verified, leaving it unchanged": "No se ha podido verificar gzip; se deja sin cambios",
"gzip could not be verified; the change was not completed": "No se ha podido verificar gzip; el cambio no se ha completado",
"gzip replaced with pigz wrapper successfully": "gzip reemplazado con el contenedor pigz correctamente",
"has": "tiene",
"has a different MAC than the backup — update any DHCP static reservation": "tiene una MAC diferente a la del backup: actualice cualquier reserva estática de DHCP",
+1 -1
View File
@@ -338,7 +338,7 @@ def remove(root, vmid):
msg_warn(f"{translate('Could not complete private network release:')} {bridge}")
incomplete = True
elif bridge:
msg_info(f"{translate('The private network is still used by another container and is kept:')} {bridge}")
msg_info(f"{translate('The private network is still used by another guest and is kept:')} {bridge}")
if not remove_owned_host_firewall(primary):
incomplete = True
release_shared_host_files(hookscripts)
+1 -1
View File
@@ -199,7 +199,7 @@ def look(states, now):
if started and notice_due(state, now):
oci_operation_notice.notify('oci_watchdog_restarted', data)
elif action == 'give-up':
print(f'CT {vmid} ({name}) keeps stopping; it is left stopped', flush=True)
print(f'CT {vmid} ({name}) could not be kept running; it is left stopped', flush=True)
oci_operation_notice.notify('oci_watchdog_failed', data)
+1 -1
View File
@@ -19,7 +19,7 @@ def choose_usb_device(ui, title, default=None, attached=()):
for row in host.usb_devices() if row['path'] not in attached]
manual_prompt = translate('Host USB node (e.g. /dev/ttyACM0 or /dev/bus/usb/003/004)')
if not options:
ui.message(translate('No USB device was found on this host.'))
ui.message(translate('No selectable USB device was found. Type the host node by hand.'))
return ui.ask(manual_prompt, default or '/dev/ttyACM0')
options.append(('manual', translate('Another node, typed by hand')))
selected = ui.choose(title, options, default if default in dict(options) else options[0][0])
+1 -1
View File
@@ -472,7 +472,7 @@ def _removal_summary(project, vmid):
if bridge and not oci_remove.bridge_in_use(bridge, set(members)):
text += ['', f"{translate('Private network targeted for release if no other guest uses it:')} {bridge}"]
elif bridge:
text += ['', f"{translate('Private network kept, because other containers still use it:')} {bridge}"]
text += ['', f"{translate('Private network kept, because other guests still use it:')} {bridge}"]
if (primary.get('deployment') or {}).get('host_firewall'):
text += ['', translate('A matching managed host firewall rule may also be removed.')]
if kept:
+7 -4
View File
@@ -140,8 +140,10 @@ def summary(member, changes):
lines += [f"- {path}" for path in changes['remove_mounts']]
lines += [f"+ {device['host_path']}" for device in changes['add_devices']]
lines += [f"- {path}" for path in changes['remove_devices']]
lines += ['', translate('The container is restarted to apply the changes. Its own data, the database '
'and the other containers of the application are not touched.')]
lines += ['', translate('A running container is restarted to apply the changes; a stopped one is left stopped. '
'The data of the application, its database and the configuration of the other '
'containers are not changed. A container volume removed here is deleted with the '
'data in it.')]
return '\n'.join(lines)
@@ -194,8 +196,9 @@ def change_recognition(project, ui, primary, member, run_lifecycle):
text = (f"{translate('Recognition')}: {labels.get(current, current)} → {labels[selected]}\n\n"
+ translate('The machine learning container is rebuilt with the image of the new choice. Its model '
'cache, the library and the database are kept. The whole application is stopped and '
'updated, as in an update; if anything fails, the previous containers and the previous '
'choice are restored.'))
'updated, as in an update. If the operation fails after a container was changed, the '
'previous containers and the previous choice are restored. An operation that stops '
'halfway is recovered from the OCI management menu.'))
if not ui.review(text, translate('Modify OCI stack'), question=translate('Change the recognition now?'), default=True):
return False
command = [sys.executable, str(project / 'remote/oci_immich_recognition.py'), str(primary['vmid']),
+1 -1
View File
@@ -68,7 +68,7 @@ class StackRecreationTests(unittest.TestCase):
{"host_path": "/dev/ttyACM0"}], "add_mounts": [
{"type": "host-bind", "container_path": "/scans", "source": "/mnt/scans"}]}
text = stack_recreation.summary(MEMBER, changes)
for expected in ("CT 129", "+ /scans", "- /media-extra", "+ /dev/ttyACM0", "are not touched"):
for expected in ("CT 129", "+ /scans", "- /media-extra", "+ /dev/ttyACM0", "are not changed"):
self.assertIn(expected, text)
@@ -1707,12 +1707,12 @@ EOF
src="$gz"
gzip_is_binary "$src" || src=/bin/gzip.original
if ! gzip_is_binary "$src"; then
ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; }
ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified; the change was not completed")"; return 1; }
src="$gz"
fi
if ! pmx_write_file "$gz.distrib" < "$src" || ! chmod 755 "$gz.distrib" || ! gzip_is_binary "$gz.distrib"; then
rm -f "$gz.distrib"
msg_error "$(translate "gzip could not be verified, leaving it unchanged")"
msg_error "$(translate "gzip could not be verified; the change was not completed")"
return 1
fi
pmx_remove_file /bin/gzip.original
@@ -1729,10 +1729,10 @@ EOF
# Never put the wrapper in place without a real gzip behind it. With the
# diversion active, a reinstall writes straight into gzip.distrib.
real=$(dpkg-divert --truename "$gz")
ensure_gzip_binary "$real" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; }
ensure_gzip_binary "$real" || { msg_error "$(translate "gzip could not be verified; the change was not completed")"; return 1; }
if ! cmp -s "$gz" /bin/pigzwrapper && ! pmx_write_file "$gz" < /bin/pigzwrapper; then
pmx_write_file "$gz" < "$real"
msg_error "$(translate "gzip could not be verified, leaving it unchanged")"
msg_error "$(translate "gzip could not be verified; the change was not completed")"
return 1
fi
msg_ok "$(translate "gzip replaced with pigz wrapper successfully")"
+2 -2
View File
@@ -1094,7 +1094,7 @@ uninstall_pigz() {
# With the diversion still active, a reinstall writes into $real.
if ! ensure_gzip_binary "$real" || ! pmx_write_file "$gz" < "$real"; then
pmx_write_file "$gz" < /bin/pigzwrapper
msg_error "$(translate "gzip could not be verified, leaving it unchanged")"
msg_error "$(translate "gzip could not be verified; the change was not completed")"
return 1
fi
pmx_record_execution "Remove gzip diversion" "dpkg-divert --local --no-rename --remove $gz"
@@ -1116,7 +1116,7 @@ uninstall_pigz() {
rm -f "$real"
done
# pigz is still what gzip runs if anything above went wrong.
ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; }
ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified; the change was not completed")"; return 1; }
pmx_remove_file /bin/pigzwrapper
pmx_edit_file /etc/vzdump.conf 's/^pigz: 1/#pigz: 1/' 2>/dev/null || true
pmx_record_execution "Purge pigz package" "apt-get purge -y pigz"
@@ -193,7 +193,7 @@
],
[
"It keeps stopping right after starting",
"An application that stops again within seconds of starting is tried five times, waiting 30 seconds, 1, 2 and 5 minutes between them. Then it is left stopped and a notification is sent"
"An application that stops again within seconds of starting, or that does not start, is tried five times, waiting 30 seconds, 1, 2 and 5 minutes between them. Then it is left stopped and a notification is sent"
]
]
}
@@ -193,7 +193,7 @@
],
[
"No deja de detenerse nada más arrancar",
"Una aplicación que vuelve a detenerse a los pocos segundos de arrancar se intenta cinco veces, con esperas de 30 segundos, 1, 2 y 5 minutos entre ellas. Después se deja parada y se envía una notificación"
"Una aplicación que vuelve a detenerse a los pocos segundos de arrancar, o que no arranca, se intenta cinco veces, con esperas de 30 segundos, 1, 2 y 5 minutos entre ellas. Después se deja parada y se envía una notificación"
]
]
}