mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-08 14:36:38 +00:00
fix: make messages say what was verified (source questions from the Italian review)
This commit is contained in:
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: {count} ProxMenux Optimierungsupdate(s) verfügbar",
|
||||
"body": "{count} ProxMenux Optimierungsupdate(s) auf diesem Host verfügbar.\n\n🛠️ Verfügbare Versionen:\n{tool_list}\n\n💡 Bewerben Sie sich unter:\n • ProxMenux Monitor → Einstellungen → ProxMenux Optimierungen\n • Oder führen Sie das Nachinstallationsmenü (Option 2) → „Verfügbare Updates anwenden“ aus.",
|
||||
"body": "{count} ProxMenux Optimierungsupdate(s) auf diesem Host verfügbar.\n\n🛠️ Verfügbare Versionen:\n{tool_list}\n\n💡 Bewerben Sie sich unter:\n • ProxMenux Monitor → Einstellungen → ProxMenux Optimierungen\n • Oder öffnen Sie das ProxMenux-Menü → Einstellungen nach der Installation von Proxmox → „Verfügbare Updates anwenden“",
|
||||
"label": "ProxMenux Optimierungsupdates verfügbar"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: {app_name} Update verfügbar{update_title_suffix}",
|
||||
"body": "{app_name} (verwaltet von ProxMenux) hat 📦 {package_count} Paketaktualisierung(en) in seinem Container ausstehend.\n{version_line}\n\n💡 Öffnen Sie ProxMenux Monitor > Einstellungen > Secure Gateway und klicken Sie zum Übernehmen auf „Aktualisieren“.\n\n🗂️ Pakete:\n{package_list}",
|
||||
"body": "{app_name} (verwaltet von ProxMenux) hat 📦 {package_count} Paketaktualisierung(en) in seinem Container ausstehend.\n{version_line}\n\n💡 Öffnen Sie ProxMenux Monitor > Sicherheit > Secure Gateway und klicken Sie zum Übernehmen auf „Aktualisieren“.\n\n🗂️ Pakete:\n{package_list}",
|
||||
"label": "Secure Gateway-Update verfügbar"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: {count} actualización(es) de optimizaciones de ProxMenux disponible(s)",
|
||||
"body": "{count} actualización(es) de optimizaciones de ProxMenux disponible(s) en este host.\n\n🛠️ Versiones disponibles:\n{tool_list}\n\n💡 Aplicar desde:\n • ProxMenux Monitor → Ajustes → ProxMenux Optimizations\n • O ejecute el menú de post-instalación (opción 2) → \"Aplicar actualizaciones disponibles\"",
|
||||
"body": "{count} actualización(es) de optimizaciones de ProxMenux disponible(s) en este host.\n\n🛠️ Versiones disponibles:\n{tool_list}\n\n💡 Aplicar desde:\n • ProxMenux Monitor → Ajustes → ProxMenux Optimizations\n • O abra el menú de ProxMenux → Configuración post instalación de Proxmox → \"Aplicar actualizaciones disponibles\"",
|
||||
"label": "Actualizaciones de optimizaciones de ProxMenux disponibles"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: actualización de {app_name} disponible{update_title_suffix}",
|
||||
"body": "{app_name} (gestionado por ProxMenux) tiene 📦 {package_count} actualización(es) de paquetes pendiente(s) en su contenedor.\n{version_line}\n\n💡 Abra ProxMenux Monitor > Ajustes > Secure Gateway y haga clic en \"Actualizar\" para aplicarlas.\n\n🗂️ Paquetes:\n{package_list}",
|
||||
"body": "{app_name} (gestionado por ProxMenux) tiene 📦 {package_count} actualización(es) de paquetes pendiente(s) en su contenedor.\n{version_line}\n\n💡 Abra ProxMenux Monitor > Seguridad > Secure Gateway y haga clic en \"Actualizar\" para aplicarlas.\n\n🗂️ Paquetes:\n{package_list}",
|
||||
"label": "Actualización de Secure Gateway disponible"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname} : {count} ProxMenux mise à jour(s) d'optimisation disponible(s)",
|
||||
"body": "{count} ProxMenux mise à jour(s) d'optimisation disponible sur cet hôte.\n\n🛠️ Versions disponibles :\n{tool_list}\n\n💡 Postulez à partir de :\n • ProxMenux Moniteur → Paramètres → ProxMenux Optimisations\n • Ou exécutez le menu post-installation (option 2) → \"Appliquer les mises à jour disponibles\"",
|
||||
"body": "{count} ProxMenux mise à jour(s) d'optimisation disponible sur cet hôte.\n\n🛠️ Versions disponibles :\n{tool_list}\n\n💡 Postulez à partir de :\n • ProxMenux Moniteur → Paramètres → ProxMenux Optimisations\n • Ou ouvrez le menu ProxMenux → Paramètres post-installation de Proxmox → \"Appliquer les mises à jour disponibles\"",
|
||||
"label": "ProxMenux mises à jour d'optimisation disponibles"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname} : {app_name} mise à jour disponible{update_title_suffix}",
|
||||
"body": "{app_name} (géré par ProxMenux) a 📦 {package_count} mise à jour(s) du package en attente dans son conteneur.\n{version_line}\n\n💡 Ouvrez ProxMenux Monitor > Paramètres > Secure Gateway et cliquez sur « Mettre à jour » pour appliquer.\n\n🗂️ Forfaits :\n{package_list}",
|
||||
"body": "{app_name} (géré par ProxMenux) a 📦 {package_count} mise à jour(s) du package en attente dans son conteneur.\n{version_line}\n\n💡 Ouvrez ProxMenux Monitor > Sécurité > Secure Gateway et cliquez sur « Mettre à jour » pour appliquer.\n\n🗂️ Forfaits :\n{package_list}",
|
||||
"label": "Mise à jour de Secure Gateway disponible"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: aggiornamenti ottimizzazioni ProxMenux disponibili: {count}",
|
||||
"body": "{count} aggiornamenti delle ottimizzazioni ProxMenux disponibili su questo host.\n\n🛠️ Versioni disponibili:\n{tool_list}\n\n💡 Per applicarli:\n • ProxMenux Monitor → Settings → ProxMenux Optimizations\n • Oppure aprire il menu post-installazione (opzione 2) → \"Apply available updates\"",
|
||||
"body": "{count} aggiornamenti delle ottimizzazioni ProxMenux disponibili su questo host.\n\n🛠️ Versioni disponibili:\n{tool_list}\n\n💡 Per applicarli:\n • ProxMenux Monitor → Settings → ProxMenux Optimizations\n • Oppure aprire il menu ProxMenux → Impostazioni post-installazione Proxmox → \"Applica aggiornamenti disponibili\"",
|
||||
"label": "Aggiornamenti ottimizzazioni ProxMenux disponibili"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: aggiornamento disponibile per {app_name}{update_title_suffix}",
|
||||
"body": "{app_name} (gestita da ProxMenux) ha 📦 {package_count} aggiornamenti dei pacchetti in sospeso nel proprio container.\n{version_line}\n\n💡 Aprire ProxMenux Monitor > Settings > Secure Gateway e fare clic su \"Update\" per applicarli.\n\n🗂️ Pacchetti:\n{package_list}",
|
||||
"body": "{app_name} (gestita da ProxMenux) ha 📦 {package_count} aggiornamenti dei pacchetti in sospeso nel proprio container.\n{version_line}\n\n💡 Aprire ProxMenux Monitor > Sicurezza > Secure Gateway e fare clic su \"Update\" per applicarli.\n\n🗂️ Pacchetti:\n{package_list}",
|
||||
"label": "Aggiornamento Secure Gateway disponibile"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: {count} ProxMenux atualizações de otimização disponíveis",
|
||||
"body": "{count} ProxMenux atualizações de otimização disponíveis neste host.\n\n🛠️ Versões disponíveis:\n{tool_list}\n\n💡 Inscreva-se em:\n • ProxMenux Monitor → Configurações → ProxMenux Otimizações\n • Ou execute o menu pós-instalação (opção 2) → \"Aplicar atualizações disponíveis\"",
|
||||
"body": "{count} ProxMenux atualizações de otimização disponíveis neste host.\n\n🛠️ Versões disponíveis:\n{tool_list}\n\n💡 Inscreva-se em:\n • ProxMenux Monitor → Configurações → ProxMenux Otimizações\n • Ou abra o menu ProxMenux → Configurações pós-instalação do Proxmox → \"Aplicar atualizações disponíveis\"",
|
||||
"label": "Atualizações de otimização ProxMenux disponíveis"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: {app_name} atualização disponível{update_title_suffix}",
|
||||
"body": "{app_name} (gerenciado por ProxMenux) tem 📦 {package_count} atualizações de pacote pendentes em seu contêiner.\n{version_line}\n\n💡 Abra ProxMenux Monitor > Configurações > Gateway Seguro e clique em \"Atualizar\" para aplicar.\n\n🗂️ Pacotes:\n{package_list}",
|
||||
"body": "{app_name} (gerenciado por ProxMenux) tem 📦 {package_count} atualizações de pacote pendentes em seu contêiner.\n{version_line}\n\n💡 Abra ProxMenux Monitor > Segurança > Gateway Seguro e clique em \"Atualizar\" para aplicar.\n\n🗂️ Pacotes:\n{package_list}",
|
||||
"label": "Atualização do Secure Gateway disponível"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -6754,12 +6754,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: {count} ProxMenux dostupných aktualizácií optimalizácií",
|
||||
"body": "Na tomto serveri je dostupných {count} aktualizácií optimalizácií ProxMenux.\n\n🛠️ Dostupné verzie:\n{tool_list}\n\n💡 Použiť cez:\n • ProxMenux Monitor → Nastavenia → Optimalizácie ProxMenux\n • Alebo spustite ponuku po inštalácii (možnosť 2) → „Použiť dostupné aktualizácie“",
|
||||
"body": "Na tomto serveri je dostupných {count} aktualizácií optimalizácií ProxMenux.\n\n🛠️ Dostupné verzie:\n{tool_list}\n\n💡 Použiť cez:\n • ProxMenux Monitor → Nastavenia → Optimalizácie ProxMenux\n • Alebo otvorte ponuku ProxMenux → Nastavenie Proxmoxu po inštalácii → „Nainštalovať dostupné aktualizácie“",
|
||||
"label": "Dostupné aktualizácie optimalizácií ProxMenux"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: {app_name} dostupná aktualizácia{update_title_suffix}",
|
||||
"body": "Aplikácia {app_name} (spravovaná cez ProxMenux) má vo svojom kontajneri 📦 {package_count} čakajúcich aktualizácií balíkov.\n{version_line}\n\n💡 Otvorte ProxMenux Monitor > Nastavenia > Secure Gateway a kliknite na „Aktualizovať“.\n\n🗂️ Balíky:\n{package_list}",
|
||||
"body": "Aplikácia {app_name} (spravovaná cez ProxMenux) má vo svojom kontajneri 📦 {package_count} čakajúcich aktualizácií balíkov.\n{version_line}\n\n💡 Otvorte ProxMenux Monitor > Bezpečnosť > Secure Gateway a kliknite na „Aktualizovať“.\n\n🗂️ Balíky:\n{package_list}",
|
||||
"label": "K dispozícii je aktualizácia Secure Gateway"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -6755,12 +6755,12 @@
|
||||
},
|
||||
"post_install_update": {
|
||||
"title": "{hostname}: {count} ProxMenux optimeringsuppdatering(ar) tillgängliga",
|
||||
"body": "{count} ProxMenux optimeringsuppdatering(ar) tillgängliga på denna värd.\n\n🛠️ Tillgängliga versioner:\n{tool_list}\n\n💡 Ansök från:\n • ProxMenux Bildskärm → Inställningar → ProxMenux Optimering\n • Eller kör efterinstallationsmenyn (alternativ 2) → \"Använd tillgängliga uppdateringar\"",
|
||||
"body": "{count} ProxMenux optimeringsuppdatering(ar) tillgängliga på denna värd.\n\n🛠️ Tillgängliga versioner:\n{tool_list}\n\n💡 Ansök från:\n • ProxMenux Bildskärm → Inställningar → ProxMenux Optimering\n • Eller öppna ProxMenux-menyn → Inställningar efter installation av Proxmox → \"Tillämpa tillgängliga uppdateringar\"",
|
||||
"label": "ProxMenux optimeringsuppdateringar tillgängliga"
|
||||
},
|
||||
"secure_gateway_update_available": {
|
||||
"title": "{hostname}: {app_name} uppdatering tillgänglig{update_title_suffix}",
|
||||
"body": "{app_name} (hanteras av ProxMenux) har 📦 {package_count} paketuppdatering(ar) väntande i sin behållare.\n{version_line}\n\n💡 Öppna ProxMenux Monitor > Inställningar > Säker gateway och klicka på \"Uppdatera\" för att ansöka.\n\n🗂️ Paket:\n{package_list}",
|
||||
"body": "{app_name} (hanteras av ProxMenux) har 📦 {package_count} paketuppdatering(ar) väntande i sin behållare.\n{version_line}\n\n💡 Öppna ProxMenux Monitor > Säkerhet > Säker gateway och klicka på \"Uppdatera\" för att ansöka.\n\n🗂️ Paket:\n{package_list}",
|
||||
"label": "Säker gateway-uppdatering tillgänglig"
|
||||
},
|
||||
"nvidia_driver_update_available": {
|
||||
|
||||
@@ -356,7 +356,7 @@ def _detect_oci_apps() -> list[dict]:
|
||||
"type": "oci_app",
|
||||
"name": app.get("name") or app_id,
|
||||
"current_version": None, # filled by checker
|
||||
"menu_label": "Settings → Secure Gateway",
|
||||
"menu_label": "Security → Secure Gateway",
|
||||
"menu_script": None, # OCI apps update via the dashboard, no bash script
|
||||
# Stash the raw app_id so the checker can find it without
|
||||
# parsing the prefixed registry id.
|
||||
|
||||
@@ -1685,7 +1685,7 @@ TEMPLATES = {
|
||||
'🛠️ Available versions:\n{tool_list}\n\n'
|
||||
'💡 Apply from:\n'
|
||||
' • ProxMenux Monitor → Settings → ProxMenux Optimizations\n'
|
||||
' • Or run the post-install menu (option 2) → "Apply available updates"'
|
||||
' • Or open the ProxMenux menu → Settings post-install Proxmox → "Apply available updates"'
|
||||
),
|
||||
'label': 'ProxMenux optimization updates available',
|
||||
'group': 'updates',
|
||||
@@ -1713,7 +1713,7 @@ TEMPLATES = {
|
||||
'{app_name} (managed by ProxMenux) has 📦 {package_count} package update(s) '
|
||||
'pending in its container.\n'
|
||||
'{version_line}\n\n'
|
||||
'💡 Open ProxMenux Monitor > Settings > Secure Gateway and click '
|
||||
'💡 Open ProxMenux Monitor > Security > Secure Gateway and click '
|
||||
'"Update" to apply.\n\n'
|
||||
'🗂️ Packages:\n{package_list}'
|
||||
),
|
||||
|
||||
@@ -3281,6 +3281,7 @@
|
||||
"Keeping GPU in source VM config": "Mantener la GPU en la configuración de la VM de origen",
|
||||
"Keeping the settings changed in Proxmox:": "Mantener la configuración cambiada en Proxmox:",
|
||||
"Kept sharedfiles group (has regular users assigned).": "Se mantiene el grupo de archivos compartidos (tiene usuarios habituales asignados).",
|
||||
"Kept sharedfiles group (it still has members that are not remapped users).": "Se conserva el grupo sharedfiles (todavía tiene miembros que no son usuarios remapeados).",
|
||||
"Kernel and architecture info": "Información sobre el kernel y la arquitectura",
|
||||
"Kernel headers and build tools verified.": "Encabezados del kernel y herramientas de compilación verificados.",
|
||||
"Kernel headers install failed — DKMS rebuild will likely fail:": "Falló la instalación de los encabezados del kernel; es probable que la reconstrucción de DKMS falle:",
|
||||
@@ -3632,6 +3633,7 @@
|
||||
"Mounted ISO on device": "ISO montado en el dispositivo",
|
||||
"Mounted at": "Montado en",
|
||||
"Mounted external disk": "Disco externo montado",
|
||||
"Mounted external disk (no network needed, single-machine dedup)": "Disco externo montado (no necesita red, desduplicación de una sola máquina)",
|
||||
"Mounted external disk (offline-safe, single-machine dedup)": "Disco externo montado (desduplicación de una sola máquina, segura sin conexión)",
|
||||
"Mounted filesystem detected": "Sistema de archivos montado detectado",
|
||||
"Mounting CIFS share...": "Montando recurso compartido CIFS...",
|
||||
@@ -4654,6 +4656,7 @@
|
||||
"ProxMenux only acts as a launcher — once the script starts, control leaves ProxMenux.": "ProxMenux solo actúa como Lanzador del script.",
|
||||
"ProxMenux saved it locally at:": "ProxMenux lo guardó localmente en:",
|
||||
"ProxMenux-managed .link file(s). User-authored .link files were left in place.": "Archivo(s) .link gestionado(s) por ProxMenux. Los archivos .link creados por el usuario se han conservado.",
|
||||
"ProxMenux-managed .link files removed: {count}. User-authored .link files were left in place.": "Archivos .link gestionados por ProxMenux eliminados: {count}. Los archivos .link creados por el usuario se han conservado.",
|
||||
"Proxmology logo applied": "Logotipo de Proxmology aplicado",
|
||||
"Proxmox 9 system update allready": "Actualización del sistema Proxmox 9 ya",
|
||||
"Proxmox APT repositories configured": "Repositorios Proxmox APT configurados",
|
||||
@@ -4816,6 +4819,7 @@
|
||||
"Recovery blob:": "Blob de recuperación:",
|
||||
"Recovery completed. The container had not been modified yet.": "Recuperación completada. El contenedor aún no había sido modificado.",
|
||||
"Recovery completed. The disks of the failed attempt were removed; the backup is kept.": "Recuperación completada. Los discos del intento fallido se han eliminado; el backup se mantiene.",
|
||||
"Recovery completed; the backup is kept.": "Recuperación completada; el backup se mantiene.",
|
||||
"Recovery failed": "La recuperación falló",
|
||||
"Recovery passphrase": "Frase de contraseña de recuperación",
|
||||
"Recovery setup failed": "Error en la configuración de recuperación",
|
||||
@@ -6115,6 +6119,7 @@
|
||||
"The disk does not belong to this CT; automatic adoption is unsafe": "El disco no pertenece a este CT; incorporarlo automáticamente no es seguro",
|
||||
"The disk size cannot be reproduced": "El tamaño del disco no se puede reproducir",
|
||||
"The disk usage of the container could not be read": "El uso del disco del contenedor no se podía leer",
|
||||
"The disks of the failed attempt could not be removed and stay in the temporary container:": "Los discos del intento fallido no se pudieron eliminar y siguen en el contenedor temporal:",
|
||||
"The domain must resolve to the public address of this network before the certificate can be issued.": "El dominio debe resolver a la dirección pública de esta red antes de poder emitir el certificado.",
|
||||
"The download client still needs to be configured.": "El cliente de descarga todavía necesita ser configurado.",
|
||||
"The download stopped progressing; cancelling this attempt.": "La descarga dejó de progresar; cancelando este intento.",
|
||||
@@ -6276,6 +6281,7 @@
|
||||
"The prepared directory escapes the rootfs:": "El directorio preparado escapa a los rootfs:",
|
||||
"The preselected VMID does not exist on this host:": "El VMID preseleccionado no existe en este host:",
|
||||
"The previous native backup will be restored. Shared host directories are not reverted. Displaced disks are kept.": "Se restaurará el backup nativo anterior. Los directorios compartidos del host no se revierten. Los discos desplazados se conservan.",
|
||||
"The previous native backup will be restored. Shared host directories are not reverted. The disks of the failed attempt are removed.": "Se restaurará el backup nativo anterior. Los directorios compartidos del host no se revierten. Los discos del intento fallido se eliminan.",
|
||||
"The previous recognition choice could not be put back:": "No se pudo volver a la opción de reconocimiento anterior:",
|
||||
"The previous stack contract is not safe; review it before reusing it": "El contrato de pila anterior no es seguro; revíselo antes de reutilizarlo",
|
||||
"The previous stack contract is not valid; it is not archived automatically": "El contrato de pila anterior no es válido; no se archiva automáticamente",
|
||||
@@ -6297,6 +6303,7 @@
|
||||
"The read-only view was not published": "La vista de solo lectura no se publicó",
|
||||
"The read/write view was not published": "La vista de lectura/escritura no se publicó",
|
||||
"The recipe requires configuration at startup; its coordinated replay is not available": "La configuración de la receta requiere al inicio; su reproducción coordinada no está disponible",
|
||||
"The recognition change of Immich did not complete:": "El cambio de reconocimiento de Immich no se completó:",
|
||||
"The recognition of Immich was not changed:": "No se cambió el reconocimiento de Immich:",
|
||||
"The record belongs to another container": "El registro pertenece a otro contenedor",
|
||||
"The record does not belong to this operation": "El registro no pertenece a esta operación",
|
||||
@@ -6456,6 +6463,7 @@
|
||||
"This NFS share is fully restricted — even the host root cannot write to it.": "Este recurso compartido NFS está completamente restringido: ni siquiera la raíz del host puede escribir en él.",
|
||||
"This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Esta máquina virtual requiere pasos de instalación adicionales; consulte la guía de instalación en:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144",
|
||||
"This action will:": "Esta acción:",
|
||||
"This application cannot be recovered yet:": "Esta aplicación todavía no se puede recuperar:",
|
||||
"This application cannot be recovered yet; nothing was changed:": "Esta aplicación todavía no se puede recuperar; no se ha cambiado nada:",
|
||||
"This application is not an Immich installed by ProxMenux": "Esta aplicación no es un Immich instalado por ProxMenux",
|
||||
"This archive does not contain a recognized backup layout.": "Este archivo no contiene un diseño de backup reconocido.",
|
||||
@@ -6472,6 +6480,7 @@
|
||||
"This container is not a member of a multi-container application": "Este contenedor no forma parte de una aplicación de varios contenedores",
|
||||
"This container is not a registered OCI instance.": "Este contenedor no es una instancia OCI registrada.",
|
||||
"This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Este contenedor se restauró o volvió de otro host después de que se escribiera su registro en este host. Abre de nuevo este menú para recuperarlo; no se ha cambiado nada.",
|
||||
"This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; the container was not modified.": "Este contenedor se restauró o volvió de otro host después de que se escribiera su registro en este host. Abre de nuevo este menú para recuperarlo; el contenedor no se ha modificado.",
|
||||
"This converts all directory UIDs/GIDs by adding 100000": "Esto convierte todos los UID/GID del directorio agregando 100000",
|
||||
"This converts all file UIDs/GIDs by adding 100000": "Esto convierte todos los archivos UID/GID agregando 100000",
|
||||
"This creates a backup in case you need to revert changes": "Esto crea un backup en caso de que necesite revertir los cambios",
|
||||
@@ -6776,6 +6785,7 @@
|
||||
"Unsupported tmpfs options": "Opciones de tmpfs sin soporte",
|
||||
"Unsupported volume options:": "Opciones de volumen no admitidas:",
|
||||
"Until they are registered again they cannot be updated or managed from here. The recovery checks first that everything they need is on this host and changes nothing otherwise.": "Hasta que se registren de nuevo no se pueden actualizar ni gestionar desde aquí. La recuperación comprueba antes que este host tiene todo lo que necesitan y, si no es así, no cambia nada.",
|
||||
"Until they are registered again they cannot be updated or managed from here. The recovery checks first that everything they need is on this host; an application that lacks something is not registered.": "Hasta que se registren de nuevo no se pueden actualizar ni gestionar desde aquí. La recuperación comprueba antes que este host tiene todo lo que necesitan; una aplicación a la que le falta algo no se registra.",
|
||||
"Untrusted or modified NVIDIA hook": "Hook NVIDIA sin confianza o modificado",
|
||||
"Unused image removed from the cache:": "Imagen sin uso eliminada de la caché:",
|
||||
"Unused images removed from the cache:": "Imágenes sin uso eliminadas de la caché:",
|
||||
|
||||
@@ -228,16 +228,18 @@ def apply(root, primary_id, vmid, images, acceleration, render_device, gfx_overr
|
||||
|
||||
|
||||
def revert(root, primary_id, vmid, images, previous, was_running):
|
||||
"""Give the container and the records the choice they had."""
|
||||
"""Give the container and the records the choice they had. Returns
|
||||
whether they have it again."""
|
||||
try:
|
||||
if oci_stack_modify.is_running(vmid):
|
||||
stop(vmid)
|
||||
apply(root, primary_id, vmid, images, *previous)
|
||||
except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error:
|
||||
msg_warn(f"{translate('The previous recognition choice could not be put back:')} {error}")
|
||||
return
|
||||
return False
|
||||
if was_running:
|
||||
subprocess.run(['pct', 'start', str(vmid)], capture_output=True, check=False)
|
||||
return True
|
||||
|
||||
|
||||
def change(root, primary_id, acceleration, render_device=None, gfx_override=None):
|
||||
@@ -262,17 +264,17 @@ def change(root, primary_id, acceleration, render_device=None, gfx_override=None
|
||||
msg_info(translate('Preparing the machine learning container for the new choice...'))
|
||||
apply(root, primary_id, vmid, images, acceleration, render_device, gfx_override)
|
||||
msg_ok(translate('Machine learning container prepared'))
|
||||
except BaseException:
|
||||
revert(root, primary_id, vmid, images, previous, was_running)
|
||||
except BaseException as error:
|
||||
error.recognition_kept = revert(root, primary_id, vmid, images, previous, was_running)
|
||||
raise
|
||||
try:
|
||||
# The image of the new choice replaces the one in use, as an update does.
|
||||
oci_stack_native.run(primary_id, acknowledge_external_data=True)
|
||||
except BaseException:
|
||||
except BaseException as error:
|
||||
with instances.locked(root):
|
||||
# An update left halfway keeps its own record of what to restore.
|
||||
if not instances.read(root, primary_id).get('pending_stack_transaction'):
|
||||
revert(root, primary_id, vmid, images, previous, was_running)
|
||||
error.recognition_kept = (not instances.read(root, primary_id).get('pending_stack_transaction')
|
||||
and revert(root, primary_id, vmid, images, previous, was_running))
|
||||
raise
|
||||
if was_running and not oci_stack_modify.is_running(vmid):
|
||||
# An update leaves each container as it found it, and this one was
|
||||
@@ -297,7 +299,12 @@ def main():
|
||||
return 1
|
||||
except (OSError, ValueError, KeyError, RuntimeError, StopIteration, subprocess.SubprocessError) as error:
|
||||
if not getattr(error, 'oci_reported', False):
|
||||
msg_error(f"{translate('The recognition of Immich was not changed:')} {error}")
|
||||
# Only an error raised before anything was touched, or one that
|
||||
# was put back, leaves the previous choice in place.
|
||||
kept = getattr(error, 'recognition_kept', True)
|
||||
message = (translate('The recognition of Immich was not changed:') if kept
|
||||
else translate('The recognition change of Immich did not complete:'))
|
||||
msg_error(f"{message} {error}")
|
||||
return 1
|
||||
msg_ok(translate('Recognition of Immich changed; the application was updated with the new choice.'))
|
||||
return 0
|
||||
|
||||
@@ -768,29 +768,33 @@ def release_stage(state, discard=False):
|
||||
"""After a commit the holder CT only keeps its own rootfs: every parked
|
||||
volume went back to the application. After a verified recovery it holds
|
||||
the disks of the failed attempt, which the restored backup replaced:
|
||||
`discard` removes them with it. Otherwise anything still attached keeps it."""
|
||||
`discard` removes them with it. Otherwise anything still attached keeps it.
|
||||
Returns whether the holder CT was removed."""
|
||||
stage = state.get('stage')
|
||||
if not stage:
|
||||
return
|
||||
return False
|
||||
try:
|
||||
config = owned(stage, 'proxmenux-transaction=' + state['id'])
|
||||
except (ValueError, RuntimeError, subprocess.CalledProcessError):
|
||||
return
|
||||
return False
|
||||
held = mounts(config) or any(re.fullmatch(r'unused[0-9]+', key) for key in parse_config(config))
|
||||
if held and not discard:
|
||||
return
|
||||
return False
|
||||
if discard:
|
||||
stop(stage)
|
||||
run('pct', 'destroy', str(stage))
|
||||
return True
|
||||
|
||||
|
||||
def discard_stage(state):
|
||||
"""Remove the holder CT of an operation that ended in a verified recovery.
|
||||
A failure here leaves it in place and does not undo the recovery."""
|
||||
A failure here leaves it in place and does not undo the recovery.
|
||||
Returns whether the holder CT was removed."""
|
||||
try:
|
||||
release_stage(state, discard=True)
|
||||
return release_stage(state, discard=True)
|
||||
except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as error:
|
||||
log(f'cleanup: {error}')
|
||||
return False
|
||||
|
||||
|
||||
def gib(size):
|
||||
@@ -1139,8 +1143,13 @@ def complete_recovery(root, journal, state):
|
||||
checkpoint(journal, state, 'rolled-back')
|
||||
if show:
|
||||
# A member of a stack is released when the whole stack is back.
|
||||
discard_stage(state)
|
||||
msg_ok(translate('Recovery completed. The disks of the failed attempt were removed; the backup is kept.'))
|
||||
if discard_stage(state):
|
||||
msg_ok(translate('Recovery completed. The disks of the failed attempt were removed; the backup is kept.'))
|
||||
else:
|
||||
msg_ok(translate('Recovery completed; the backup is kept.'))
|
||||
if state.get('stage'):
|
||||
msg_warn(f"{translate('The disks of the failed attempt could not be removed and stay in the temporary container:')} "
|
||||
f"CT {state['stage']}")
|
||||
if state.get('original_host_sources') or state.get('desired_host_sources'):
|
||||
msg_info2(translate('Shared host files are kept as they are; the backup does not restore their content.'))
|
||||
|
||||
|
||||
@@ -943,7 +943,7 @@ def start_order(entry):
|
||||
return int(order[1]) if order else 9999
|
||||
|
||||
|
||||
def recover(root, plan, start=False, host_firewall=False):
|
||||
def recover(root, plan, start=False, host_firewall=()):
|
||||
for bridge, subnet in plan['bridges'].items():
|
||||
msg_info(f"{translate('Creating the private network...')} {bridge}")
|
||||
create_bridge(bridge, subnet)
|
||||
@@ -974,7 +974,8 @@ def recover(root, plan, start=False, host_firewall=False):
|
||||
if not rule:
|
||||
continue
|
||||
text = f"TCP {rule['port']} {translate('from')} {rule['source']}"
|
||||
if not host_firewall:
|
||||
# Each rule is confirmed on its own; one answer does not cover the rest.
|
||||
if rule['vmid'] not in host_firewall:
|
||||
plan['notes'].append(f"CT {entry['vmid']}: {translate('its host firewall rule was not added:')} {text}")
|
||||
elif add_firewall_rule(rule):
|
||||
msg_ok(f"{translate('Host firewall rule added:')} {text}")
|
||||
@@ -1020,7 +1021,8 @@ def main():
|
||||
parser.add_argument('action', choices=('list', 'plan', 'recover'))
|
||||
parser.add_argument('--root', type=Path, default=instances.ROOT)
|
||||
parser.add_argument('--start', action='store_true')
|
||||
parser.add_argument('--host-firewall', action='store_true')
|
||||
parser.add_argument('--host-firewall', type=int, action='append', default=[], metavar='VMID',
|
||||
help='Add the host firewall rule of this container; one option for each confirmed rule')
|
||||
parser.add_argument('--automatic', action='store_true',
|
||||
help='Only the applications that need no question: the ones the cluster keeps a record of')
|
||||
args = parser.parse_args()
|
||||
@@ -1041,13 +1043,13 @@ def main():
|
||||
failed = 0
|
||||
if args.automatic:
|
||||
plans = [plan for plan in plans if automatic(plan)]
|
||||
args.start = args.host_firewall = False
|
||||
args.start, args.host_firewall = False, []
|
||||
for plan in plans:
|
||||
containers = ', '.join(f"CT {entry['vmid']}" for entry in plan['restored'])
|
||||
msg_info2(f"{plan['title']} · {containers}")
|
||||
if plan['blockers']:
|
||||
failed += 1
|
||||
msg_error(translate('This application cannot be recovered yet; nothing was changed:'))
|
||||
msg_error(translate('This application cannot be recovered yet:'))
|
||||
for line in plan['blockers']:
|
||||
msg_info2(f" - {line}")
|
||||
lost = [entry for entry in plan['restored'] if entry.get('unrecoverable')]
|
||||
|
||||
@@ -186,7 +186,7 @@ def offer_recovery(project, ui):
|
||||
lines = '\n'.join(f" CT {row['vmid']} {row['hostname']}" for row in found)
|
||||
text = (f"{translate('These containers were restored from a backup and this host has no record of their OCI application:')}"
|
||||
f"\n\n{lines}\n\n"
|
||||
f"{translate('Until they are registered again they cannot be updated or managed from here. The recovery checks first that everything they need is on this host and changes nothing otherwise.')}"
|
||||
f"{translate('Until they are registered again they cannot be updated or managed from here. The recovery checks first that everything they need is on this host; an application that lacks something is not registered.')}"
|
||||
f"\n\n{translate('Recover them now?')}")
|
||||
if not ui.confirm(text, default=True):
|
||||
return
|
||||
@@ -194,8 +194,7 @@ def offer_recovery(project, ui):
|
||||
for rule in _restored_firewall_rules(project):
|
||||
if ui.confirm(translate('CT {vmid} is a host monitor and had a rule in the host firewall. Allow TCP port {port} from {subnet} through the firewall of this host? Existing firewall rules are not changed.').format(
|
||||
vmid=rule['vmid'], port=rule['port'], subnet=rule['source']), default=False):
|
||||
command.append('--host-firewall')
|
||||
break
|
||||
command.extend(['--host-firewall', str(rule['vmid'])])
|
||||
if ui.confirm(translate('Start the applications once they are registered? Answer No if the original containers are still running on another host: both would use the same addresses.'),
|
||||
default=False):
|
||||
command.append('--start')
|
||||
@@ -277,7 +276,7 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
# A container that came back from another node or from an older backup
|
||||
# carries the record that describes it; the one of this host is not used.
|
||||
if carry_records(project, vmids=[row['vmid']], verify=True).get(row['vmid']) == 'stale':
|
||||
ui.message(translate('This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.'),
|
||||
ui.message(translate('This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; the container was not modified.'),
|
||||
translate('OCI management'))
|
||||
return False
|
||||
row = check_selected(project, row)
|
||||
@@ -370,7 +369,7 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
||||
if action is None:
|
||||
return False
|
||||
if action == 'recover' and not ui.review(
|
||||
translate('The previous native backup will be restored. Shared host directories are not reverted. Displaced disks are kept.'),
|
||||
translate('The previous native backup will be restored. Shared host directories are not reverted. The disks of the failed attempt are removed.'),
|
||||
translate('Recover OCI'), question=translate('Recover now?'), default=True):
|
||||
return False
|
||||
return _run_lifecycle([sys.executable, str(project / 'remote/oci_instance_transaction.py'),
|
||||
|
||||
@@ -171,7 +171,8 @@ class RevertTests(unittest.TestCase):
|
||||
patch.object(recognition, "stop", side_effect=lambda vmid: calls.append(("stop", vmid))), \
|
||||
patch.object(recognition, "apply", side_effect=lambda *arguments: calls.append(("apply",) + arguments[2:])), \
|
||||
patch.object(recognition.subprocess, "run", side_effect=lambda command, **_: calls.append(tuple(command[:3]))):
|
||||
recognition.revert(Path("/nonexistent"), 100, 101, {"cpu": "image"}, ("cpu", None, None), True)
|
||||
kept = recognition.revert(Path("/nonexistent"), 100, 101, {"cpu": "image"}, ("cpu", None, None), True)
|
||||
self.assertTrue(kept)
|
||||
self.assertEqual(calls, [("stop", 101), ("apply", 101, {"cpu": "image"}, "cpu", None, None),
|
||||
("pct", "start", "101")])
|
||||
|
||||
@@ -180,9 +181,26 @@ class RevertTests(unittest.TestCase):
|
||||
patch.object(recognition, "apply", side_effect=RuntimeError("pct set: locked")), \
|
||||
patch.object(recognition, "msg_warn") as warned, \
|
||||
patch.object(recognition.subprocess, "run") as started:
|
||||
recognition.revert(Path("/nonexistent"), 100, 101, {}, ("cpu", None, None), True)
|
||||
kept = recognition.revert(Path("/nonexistent"), 100, 101, {}, ("cpu", None, None), True)
|
||||
self.assertIn("pct set: locked", warned.call_args[0][0])
|
||||
started.assert_not_called()
|
||||
self.assertFalse(kept)
|
||||
|
||||
def test_the_error_only_says_nothing_changed_when_the_choice_was_kept(self):
|
||||
def reported(error):
|
||||
with patch.object(recognition.os, "geteuid", return_value=0), \
|
||||
patch.object(recognition.sys, "argv", ["x", "100", "--acceleration", "cpu"]), \
|
||||
patch.object(recognition, "change", side_effect=error), \
|
||||
patch.object(recognition, "translate", side_effect=lambda text: text), \
|
||||
patch.object(recognition, "msg_error") as shown:
|
||||
self.assertEqual(recognition.main(), 1)
|
||||
return shown.call_args[0][0]
|
||||
self.assertIn("was not changed", reported(ValueError("already on that choice")))
|
||||
put_back = RuntimeError("update failed"); put_back.recognition_kept = True
|
||||
self.assertIn("was not changed", reported(put_back))
|
||||
left = RuntimeError("update failed"); left.recognition_kept = False
|
||||
self.assertIn("did not complete", reported(left))
|
||||
self.assertNotIn("was not changed", reported(left))
|
||||
|
||||
|
||||
class RocmProbeTests(unittest.TestCase):
|
||||
|
||||
@@ -546,5 +546,55 @@ class RegistrationTests(unittest.TestCase):
|
||||
self.assertNotIn("deployment", services[2])
|
||||
|
||||
|
||||
class FirewallConsentTests(unittest.TestCase):
|
||||
"""A host firewall rule is added only for the container it was confirmed for."""
|
||||
|
||||
def recover(self, confirmed):
|
||||
rule = lambda vmid, port: {"vmid": vmid, "port": port, "source": "192.0.2.0/24"}
|
||||
plan = {"bridges": {}, "renumbered": {}, "hold": False, "notes": [],
|
||||
"restored": [{"vmid": 120, "firewall": rule(120, 61208)}, {"vmid": 121, "firewall": rule(121, 19999)}]}
|
||||
added = []
|
||||
with patch.object(recovery, "restore_host_files"), patch.object(recovery, "clean_format_directories"), \
|
||||
patch.object(recovery, "register", return_value=[120, 121]), patch.object(recovery.carried, "carry"), \
|
||||
patch.object(recovery, "add_firewall_rule", side_effect=lambda rule: added.append(rule["vmid"]) or True), \
|
||||
patch.object(recovery, "msg_info"), patch.object(recovery, "msg_ok"):
|
||||
recovery.recover(Path("/nonexistent"), plan, host_firewall=confirmed)
|
||||
return added, plan["notes"]
|
||||
|
||||
def test_one_confirmed_rule_does_not_add_the_others(self):
|
||||
added, notes = self.recover([121])
|
||||
self.assertEqual(added, [121])
|
||||
self.assertEqual([note[:7] for note in notes], ["CT 120:"])
|
||||
|
||||
def test_without_an_answer_no_rule_is_added(self):
|
||||
added, notes = self.recover([])
|
||||
self.assertEqual(added, [])
|
||||
self.assertEqual(len(notes), 2)
|
||||
|
||||
def test_the_menu_asks_for_each_rule_and_passes_only_the_confirmed_ones(self):
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
from proxmenux_oci import management
|
||||
rules = [{"vmid": 120, "port": 61208, "source": "192.0.2.0/24"},
|
||||
{"vmid": 121, "port": 19999, "source": "192.0.2.0/24"}]
|
||||
answers = iter([True, False, True, False]) # recover, rule of 120, rule of 121, start
|
||||
|
||||
class Ui:
|
||||
asked = []
|
||||
|
||||
def confirm(self, text, default=False):
|
||||
self.asked.append(text)
|
||||
return next(answers)
|
||||
|
||||
ui = Ui()
|
||||
with patch.object(management, "restored_applications", return_value=[{"vmid": 120, "hostname": "glances"},
|
||||
{"vmid": 121, "hostname": "netdata"}]), \
|
||||
patch.object(management, "_restored_firewall_rules", return_value=rules), \
|
||||
patch.object(management, "_run_lifecycle") as run:
|
||||
management.offer_recovery(ROOT, ui)
|
||||
command = run.call_args[0][0]
|
||||
self.assertEqual(len(ui.asked), 4)
|
||||
self.assertEqual(command[command.index("--host-firewall"):], ["--host-firewall", "121"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -54,8 +54,24 @@ class StageReleaseTests(unittest.TestCase):
|
||||
patch.object(transaction, "stop"), \
|
||||
patch.object(transaction, "run", side_effect=RuntimeError("pct failed with exit code 255")), \
|
||||
patch.object(transaction, "log") as logged:
|
||||
transaction.discard_stage(STATE)
|
||||
removed = transaction.discard_stage(STATE)
|
||||
self.assertIn("pct failed", logged.call_args[0][0])
|
||||
self.assertFalse(removed)
|
||||
|
||||
def test_the_result_says_whether_the_container_was_removed(self):
|
||||
with patch.object(transaction, "owned", return_value=HOLDING), patch.object(transaction, "stop"), \
|
||||
patch.object(transaction, "run"):
|
||||
self.assertTrue(transaction.discard_stage(STATE))
|
||||
self.assertFalse(transaction.release_stage(STATE))
|
||||
with patch.object(transaction, "owned", side_effect=ValueError("not ours")):
|
||||
self.assertFalse(transaction.discard_stage(STATE))
|
||||
self.assertFalse(transaction.discard_stage({"id": "x"}))
|
||||
|
||||
def test_the_recovery_only_says_the_disks_were_removed_when_they_were(self):
|
||||
source = (ROOT / "remote/oci_instance_transaction.py").read_text()
|
||||
claim = source.index("The disks of the failed attempt were removed")
|
||||
self.assertIn("if discard_stage(state):", source[source.rindex("\n", 0, claim - 80):claim])
|
||||
self.assertIn("could not be removed and stay in the temporary container", source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2507,7 +2507,7 @@ hb_configure_borg_manual() {
|
||||
--menu "\n$(hb_translate "Select repository destination:")" \
|
||||
"$HB_UI_MENU_H" "$HB_UI_MENU_W" "$HB_UI_MENU_LIST" \
|
||||
"remote" "$(hb_translate 'Remote server via SSH (recommended — off-host, dedup across machines)')" \
|
||||
"usb" "$(hb_translate 'Mounted external disk (offline-safe, single-machine dedup)')" \
|
||||
"usb" "$(hb_translate 'Mounted external disk (no network needed, single-machine dedup)')" \
|
||||
"local" "$(hb_translate 'Local directory (single-machine — only use if it is a SEPARATE disk)')" \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
|
||||
@@ -742,7 +742,12 @@ uninstall_persistent_network() {
|
||||
done < <(pmx_uninstall_persistent_network)
|
||||
|
||||
if (( removed > 0 )); then
|
||||
msg_ok "$(translate "Removed") $removed $(translate "ProxMenux-managed .link file(s). User-authored .link files were left in place.")"
|
||||
local removed_message
|
||||
removed_message="$(translate "ProxMenux-managed .link files removed: {count}. User-authored .link files were left in place.")"
|
||||
if [[ "$removed_message" != *'{count}'* ]]; then
|
||||
removed_message="ProxMenux-managed .link files removed: {count}. User-authored .link files were left in place."
|
||||
fi
|
||||
msg_ok "${removed_message//\{count\}/$removed}"
|
||||
msg_info "$(translate "Interface names will return to default systemd behavior.")"
|
||||
NECESSARY_REBOOT=1
|
||||
else
|
||||
|
||||
@@ -551,7 +551,7 @@ uninstall_nfs() {
|
||||
pct exec "$CTID" -- groupdel sharedfiles 2>/dev/null || true
|
||||
msg_ok "$(translate "Removed sharedfiles group.")"
|
||||
else
|
||||
msg_warn "$(translate "Kept sharedfiles group (has regular users assigned).")"
|
||||
msg_warn "$(translate "Kept sharedfiles group (it still has members that are not remapped users).")"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user