fix(oci): the access prompt says what it grants, and two checks name what differs

This commit is contained in:
MacRimi
2026-10-09 00:43:32 +02:00
parent 316eb2f104
commit 16ab9808ac
6 changed files with 26 additions and 16 deletions
+6 -4
View File
@@ -189,7 +189,7 @@
"Access bridge for Nextcloud": "Puente de acceso para Nextcloud", "Access bridge for Nextcloud": "Puente de acceso para Nextcloud",
"Access bridge for Paperless": "Puente de acceso para Paperless", "Access bridge for Paperless": "Puente de acceso para Paperless",
"Access bridge for Tandoor": "Puente de acceso para Tandoor", "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 granted to the application on:": "Acceso concedido a la aplicación en:",
"Access profile:": "Perfil de acceso:", "Access profile:": "Perfil de acceso:",
"Access token of the Jupyter Lab web interface": "Token de acceso de la interfaz web de Jupyter Lab", "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 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 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 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 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 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.", "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)", "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 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.", "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", "— 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.", "— 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)", "— 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 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.", "⚠ 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 /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"
} }
+1 -1
View File
@@ -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") MOUNT_NOTES+=("$(translate "Access granted to the application on:") $SOURCE")
else else
oci_log "Could not grant access with an ACL: $SOURCE" 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
fi fi
if [[ -d $SOURCE ]]; then if [[ -d $SOURCE ]]; then
+3 -1
View File
@@ -520,7 +520,9 @@ def preflight(record, candidate, config, coordinated=None):
else: else:
raise ValueError(translate('Unsupported mount type')) raise ValueError(translate('Unsupported mount type'))
paths.append(target) 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')) raise ValueError(translate('The container disks do not match the saved record'))
if desired['rootfs'] != deployment['rootfs']: if desired['rootfs'] != deployment['rootfs']:
raise ValueError(translate('Changing the rootfs or its storage requires a separate migration')) raise ValueError(translate('Changing the rootfs or its storage requires a separate migration'))
+1 -1
View File
@@ -92,7 +92,7 @@ def check(config, deployment, vmid):
includes = [line.split(': ',1)[1] for line in lines if line.startswith('lxc.include: ')] includes = [line.split(': ',1)[1] for line in lines if line.startswith('lxc.include: ')]
if deployment.get('host_monitor'): if deployment.get('host_monitor'):
if not host_monitor_include(includes): 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 return
content = sysctl_content(deployment) content = sysctl_content(deployment)
allowed = [[str(include_path(vmid))], [str(legacy_include_path(vmid))]] if content else [[]] allowed = [[str(include_path(vmid))], [str(legacy_include_path(vmid))]] if content else [[]]
+9 -5
View File
@@ -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 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): 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 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( if ui.confirm(translate(
"{path} already exists and belongs to another user of the host, so {app} cannot " "{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 " "write to it.\n\nProxMenux can add access rules (ACL) to this directory, to "
"on the host (UID {uid}). Nothing that is there changes owner or permissions: " "everything inside it and to what is created there later, for the users and groups "
"whatever reads or writes those files today keeps doing so, and the owner of the " "{app} runs as on the host (IDs {ids}). The owner of the files does not change and "
"directory also gets access to the files {app} creates.\n\nGrant {app} access to " "whatever reads or writes them today keeps doing so; the owner of the directory "
"this directory?").format(path=mount["source"], app=title, uid=uid), True): "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 mount["grant_access"] = True
from .custom_mounts import validate_mount from .custom_mounts import validate_mount
mount["container_path"] = validate_mount(mount, [*mounts, *added]) mount["container_path"] = validate_mount(mount, [*mounts, *added])
+6 -4
View File
@@ -145,8 +145,9 @@ class SharedDirectoriesTests(unittest.TestCase):
question = next(text for text in ui.asked if "Grant ShareCoveX access" in text) 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. # The identity is the one of the volumes, in the range of an unprivileged container.
check.assert_called_once_with(directory, 101000, 101000) check.assert_called_once_with(directory, 101000, 101000)
self.assertIn("UID 101000", question) # The identity and the root of the container, as the host sees them.
self.assertIn("changes owner or permissions", question) 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) self.assertIs(plan["mounts"][1]["grant_access"], True)
_ui, _template, declined = self.build({**answers, question: [False]}) _ui, _template, declined = self.build({**answers, question: [False]})
self.assertNotIn("grant_access", declined["mounts"][1]) self.assertNotIn("grant_access", declined["mounts"][1])
@@ -162,9 +163,10 @@ class SharedDirectoriesTests(unittest.TestCase):
first = build_deployment(template, ui, ADVANCED_MODE) first = build_deployment(template, ui, ADVANCED_MODE)
prompt = next(text for text in ui.asked if nfsv4 in text) prompt = next(text for text in ui.asked if nfsv4 in text)
check.reset_mock() check.reset_mock()
build_deployment(self.catalog.compose("sharecovex"), ScriptedUI({**answers, prompt: [True]}), privileged = ScriptedUI({**answers, prompt: [True]})
ADVANCED_MODE) build_deployment(self.catalog.compose("sharecovex"), privileged, ADVANCED_MODE)
check.assert_called_once_with(directory, 1000, 1000) check.assert_called_once_with(directory, 1000, 1000)
self.assertTrue(any("(IDs 1000)" in text for text in privileged.asked))
self.assertIsNotNone(first) self.assertIsNotNone(first)
def test_a_directory_that_does_not_exist_yet_is_created_for_the_application(self, *_): def test_a_directory_that_does_not_exist_yet_is_created_for_the_application(self, *_):