diff --git a/lang/es.json b/lang/es.json index 6487bebb..efc973bb 100644 --- a/lang/es.json +++ b/lang/es.json @@ -189,7 +189,7 @@ "Access bridge for Nextcloud": "Puente de acceso para Nextcloud", "Access bridge for Paperless": "Puente de acceso para Paperless", "Access bridge for Tandoor": "Puente de acceso para Tandoor", - "Access could not be granted; the application cannot write to:": "No se pudo conceder el acceso; la aplicación no puede escribir en:", + "Access could not be granted completely; the application may be unable to write to:": "No se pudo conceder el acceso por completo; puede que la aplicación no pueda escribir en:", "Access granted to the application on:": "Acceso concedido a la aplicación en:", "Access profile:": "Perfil de acceso:", "Access token of the Jupyter Lab web interface": "Token de acceso de la interfaz web de Jupyter Lab", @@ -6175,7 +6175,7 @@ "The container devices do not match the saved record": "Los dispositivos del contenedor no coinciden con el registro guardado", "The container did not stop to update its persistent configuration:": "El contenedor no se detuvo para actualizar su configuración persistente:", "The container did not stop; its disks are not touched": "El contenedor no se detuvo; sus discos no se tocan", - "The container disks do not match the saved record": "Los discos de contenedores no coinciden con el disco guardado", + "The container disks do not match the saved record": "Los discos del contenedor no coinciden con el registro guardado", "The container does not exist:": "El contenedor no existe:", "The container does not have the fuse=1 feature enabled": "El contenedor no tiene activada la función fuse=1", "The container does not need it any more; an update downloads the new version when there is one.": "El contenedor ya no lo necesita; una actualización descarga la nueva versión cuando la hay.", @@ -7885,7 +7885,7 @@ "zfs-auto-snapshot removed (existing snapshots preserved on the pool)": "zfs-auto-snapshot eliminado (las instantáneas existentes se conservan en el grupo)", "zpool command not found. Install zfsutils-linux and retry.": "Comando zpool no encontrado. Instale zfsutils-linux y vuelva a intentarlo.", "zpool not found. Install zfsutils-linux and retry.": "zpool no encontrado. Instale zfsutils-linux y vuelva a intentarlo.", - "{path} already exists and belongs to another user of the host, so {app} cannot write to it.\n\nProxMenux can add an access rule (ACL) for the identity {app} uses on the host (UID {uid}). Nothing that is there changes owner or permissions: whatever reads or writes those files today keeps doing so, and the owner of the directory also gets access to the files {app} creates.\n\nGrant {app} access to this directory?": "{path} ya existe y pertenece a otro usuario del host, así que {app} no puede escribir en él.\n\nProxMenux puede añadir una regla de acceso (ACL) para la identidad que {app} usa en el host (UID {uid}). Nada de lo que hay cambia de propietario ni de permisos: lo que hoy lee o escribe esos archivos lo sigue haciendo, y el propietario del directorio también tiene acceso a los archivos que cree {app}.\n\n¿Dar acceso a {app} a este directorio?", + "{path} already exists and belongs to another user of the host, so {app} cannot write to it.\n\nProxMenux can add access rules (ACL) to this directory, to everything inside it and to what is created there later, for the users and groups {app} runs as on the host (IDs {ids}). The owner of the files does not change and whatever reads or writes them today keeps doing so; the owner of the directory also gets access to the files {app} creates.\n\nGrant {app} access to this directory?": "{path} ya existe y pertenece a otro usuario del host, así que {app} no puede escribir en él.\n\nProxMenux puede añadir reglas de acceso (ACL) a este directorio, a todo lo que contiene y a lo que se cree en él después, para los usuarios y grupos con los que {app} se ejecuta en el host (ID {ids}). El propietario de los archivos no cambia y lo que hoy los lee o escribe lo sigue haciendo; el propietario del directorio también tiene acceso a los archivos que cree {app}.\n\n¿Dar acceso a {app} a este directorio?", "— Full/Custom restore will skip kernel-tied paths": "— La restauración completa/personalizada omitirá las rutas vinculadas al kernel", "— disk may be busy. Skipping fstab removal.": "— el disco puede estar ocupado. Saltarse la eliminación de fstab.", "— restore proceeds as same-version (verified safe)": "- la restauración procede como la misma versión (segura verificada)", @@ -7917,5 +7917,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Los datos del disco NO se borrarán.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ El disco se desmontará y se eliminará de /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ Se eliminará la entrada /etc/fstab.", - "⚠ The disk will be unmounted.": "⚠ El disco se desmontará." + "⚠ The disk will be unmounted.": "⚠ El disco se desmontará.", + "The privilege mode of the container does not match the saved record": "El modo de privilegios del contenedor no coincide con el registro guardado", + "The host monitor include is unknown or differs from the saved record": "El include del monitor del host es desconocido o difiere del registro guardado" } diff --git a/oci/remote/install_oci.sh b/oci/remote/install_oci.sh index d25216de..83796de9 100755 --- a/oci/remote/install_oci.sh +++ b/oci/remote/install_oci.sh @@ -1876,7 +1876,7 @@ while IFS=$'\t' read -r TYPE TARGET SOURCE SIZE BACKUP READ_ONLY CREATE_IF_MISSI MOUNT_NOTES+=("$(translate "Access granted to the application on:") $SOURCE") else oci_log "Could not grant access with an ACL: $SOURCE" - MOUNT_WARNINGS+=("$(translate "Access could not be granted; the application cannot write to:") $SOURCE") + MOUNT_WARNINGS+=("$(translate "Access could not be granted completely; the application may be unable to write to:") $SOURCE") fi fi if [[ -d $SOURCE ]]; then diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index c667673c..d9941473 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -520,7 +520,9 @@ def preflight(record, candidate, config, coordinated=None): else: raise ValueError(translate('Unsupported mount type')) paths.append(target) - if cfg.get('unprivileged', '0') != ('1' if unprivileged else '0') or ':' not in cfg.get('rootfs', ''): + if cfg.get('unprivileged', '0') != ('1' if unprivileged else '0'): + raise ValueError(translate('The privilege mode of the container does not match the saved record')) + if ':' not in cfg.get('rootfs', ''): raise ValueError(translate('The container disks do not match the saved record')) if desired['rootfs'] != deployment['rootfs']: raise ValueError(translate('Changing the rootfs or its storage requires a separate migration')) diff --git a/oci/remote/oci_runtime_settings.py b/oci/remote/oci_runtime_settings.py index 94433f07..712ffce5 100644 --- a/oci/remote/oci_runtime_settings.py +++ b/oci/remote/oci_runtime_settings.py @@ -92,7 +92,7 @@ def check(config, deployment, vmid): includes = [line.split(': ',1)[1] for line in lines if line.startswith('lxc.include: ')] if deployment.get('host_monitor'): if not host_monitor_include(includes): - raise ValueError(translate('The sysctl include is unknown or differs from the saved record')) + raise ValueError(translate('The host monitor include is unknown or differs from the saved record')) return content = sysctl_content(deployment) allowed = [[str(include_path(vmid))], [str(legacy_include_path(vmid))]] if content else [[]] diff --git a/oci/src/proxmenux_oci/installer.py b/oci/src/proxmenux_oci/installer.py index 1b5e87ae..0705ac6f 100644 --- a/oci/src/proxmenux_oci/installer.py +++ b/oci/src/proxmenux_oci/installer.py @@ -194,13 +194,17 @@ def ask_shared_directories(ui, template: dict[str, Any], mounts: list[dict[str, uid, gid = int(owner.get("uid", 0)) + shift, int(owner.get("gid", 0)) + shift if os.path.isdir(mount["source"]) and not host.can_write(mount["source"], uid, gid): title = source_text(template["catalog_ui"]["title"]) or app_id + # The rules name the identity of the application and, in an + # unprivileged container, the one its root has on the host. + ids = ", ".join(str(value) for value in dict.fromkeys([uid, gid, *([shift] if shift else [])])) if ui.confirm(translate( "{path} already exists and belongs to another user of the host, so {app} cannot " - "write to it.\n\nProxMenux can add an access rule (ACL) for the identity {app} uses " - "on the host (UID {uid}). Nothing that is there changes owner or permissions: " - "whatever reads or writes those files today keeps doing so, and the owner of the " - "directory also gets access to the files {app} creates.\n\nGrant {app} access to " - "this directory?").format(path=mount["source"], app=title, uid=uid), True): + "write to it.\n\nProxMenux can add access rules (ACL) to this directory, to " + "everything inside it and to what is created there later, for the users and groups " + "{app} runs as on the host (IDs {ids}). The owner of the files does not change and " + "whatever reads or writes them today keeps doing so; the owner of the directory " + "also gets access to the files {app} creates.\n\nGrant {app} access to this " + "directory?").format(path=mount["source"], app=title, ids=ids), True): mount["grant_access"] = True from .custom_mounts import validate_mount mount["container_path"] = validate_mount(mount, [*mounts, *added]) diff --git a/oci/tests/test_shared_directories.py b/oci/tests/test_shared_directories.py index 163452ba..d8204835 100644 --- a/oci/tests/test_shared_directories.py +++ b/oci/tests/test_shared_directories.py @@ -145,8 +145,9 @@ class SharedDirectoriesTests(unittest.TestCase): question = next(text for text in ui.asked if "Grant ShareCoveX access" in text) # The identity is the one of the volumes, in the range of an unprivileged container. check.assert_called_once_with(directory, 101000, 101000) - self.assertIn("UID 101000", question) - self.assertIn("changes owner or permissions", question) + # The identity and the root of the container, as the host sees them. + self.assertIn("(IDs 101000, 100000)", question) + self.assertIn("everything inside it and to what is created there later", question) self.assertIs(plan["mounts"][1]["grant_access"], True) _ui, _template, declined = self.build({**answers, question: [False]}) self.assertNotIn("grant_access", declined["mounts"][1]) @@ -162,9 +163,10 @@ class SharedDirectoriesTests(unittest.TestCase): first = build_deployment(template, ui, ADVANCED_MODE) prompt = next(text for text in ui.asked if nfsv4 in text) check.reset_mock() - build_deployment(self.catalog.compose("sharecovex"), ScriptedUI({**answers, prompt: [True]}), - ADVANCED_MODE) + privileged = ScriptedUI({**answers, prompt: [True]}) + build_deployment(self.catalog.compose("sharecovex"), privileged, ADVANCED_MODE) check.assert_called_once_with(directory, 1000, 1000) + self.assertTrue(any("(IDs 1000)" in text for text in privileged.asked)) self.assertIsNotNone(first) def test_a_directory_that_does_not_exist_yet_is_created_for_the_application(self, *_):