Merge branch 'stretch-unstable' into patch-1

This commit is contained in:
Alexandre Aubin 2019-09-20 13:59:21 +02:00 committed by GitHub
commit b8e973e10f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 769 additions and 104 deletions

View file

@ -1,19 +1,19 @@
[![Build Status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost) [![Build status](https://travis-ci.org/YunoHost/yunohost.svg?branch=stretch-unstable)](https://travis-ci.org/YunoHost/yunohost)
[![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE)
# YunoHost core # YunoHost core
This repository is the core of YunoHost code. This repository is the core of YunoHost code.
- [YunoHost project website](https://yunohost.org) - [Project website](https://yunohost.org)
- [Butracker](https://github.com/YunoHost/issues). - [Bugtracker](https://github.com/YunoHost/issues).
## Contributing ## Contributing
- You can develop on this repository using [ynh-dev](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command. - You can develop on this repository using [ynh-dev](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command.
- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <- testing <- unstable <- your_branch`. - On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable ← testing ← unstable ← your_branch`.
- Note: if you modify python scripts, you will have to modifiy the actions map. - Note: If you modify Python scripts, you will have to modifiy the actions map.
- You can help with translation on [our translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget) - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)
<img src="https://translate.yunohost.org/widgets/yunohost/-/multi-auto.svg" alt="Translation status" /> <img src="https://translate.yunohost.org/widgets/yunohost/-/multi-auto.svg" alt="Translation status" />
@ -34,7 +34,7 @@ This repository is the core of YunoHost code.
- Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette): - Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette):
- [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command. - [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command.
- [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented). - [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented).
- You can find more details about how YunoHost works on this [documentation (in french)](https://yunohost.org/#/package_list_fr). - You can find more details about how YunoHost works on this [documentation (in French)](https://yunohost.org/#/package_list_fr).
## Dependencies ## Dependencies
@ -45,4 +45,4 @@ This repository is the core of YunoHost code.
## License ## License
As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license. As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is licensed GNU AGPL v3.

View file

@ -339,7 +339,7 @@ ynh_backup_if_checksum_is_different () {
backup_file_checksum="" backup_file_checksum=""
if [ -n "$checksum_value" ] if [ -n "$checksum_value" ]
then # Proceed only if a value was stored into the app settings then # Proceed only if a value was stored into the app settings
if ! echo "$checksum_value $file" | sudo md5sum -c --status if [ -e $file ] && ! echo "$checksum_value $file" | sudo md5sum -c --status
then # If the checksum is now different then # If the checksum is now different
backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
sudo mkdir -p "$(dirname "$backup_file_checksum")" sudo mkdir -p "$(dirname "$backup_file_checksum")"

View file

@ -23,7 +23,7 @@
"app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'",
"app_location_unavailable": "This url is not available or conflicts with an already installed app", "app_location_unavailable": "This url is not available or conflicts with an already installed app",
"app_manifest_invalid": "Invalid app manifest: {error}", "app_manifest_invalid": "Invalid app manifest: {error}",
"app_no_upgrade": "البرمجيات لا تحتاج إلى تحديث", "app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث",
"app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح", "app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح",
"app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب", "app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب",
"app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد", "app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد",
@ -37,17 +37,17 @@
"app_unsupported_remote_type": "Unsupported remote type used for the app", "app_unsupported_remote_type": "Unsupported remote type used for the app",
"app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…", "app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…",
"app_upgrade_failed": "تعذرت عملية ترقية {app:s}", "app_upgrade_failed": "تعذرت عملية ترقية {app:s}",
"app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض البرمجيات", "app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات",
"app_upgraded": "تم تحديث التطبيق {app:s}", "app_upgraded": "تم تحديث التطبيق {app:s}",
"appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.",
"appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.",
"appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}", "appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}",
"appslist_migrating": "Migrating application list {appslist:s} …", "appslist_migrating": "Migrating application list {appslist:s} …",
"appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.",
"appslist_removed": "تم حذف قائمة البرمجيات {appslist:s}", "appslist_removed": "تم حذف قائمة التطبيقات {appslist:s}",
"appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid",
"appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}", "appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}",
"appslist_unknown": "قائمة البرمجيات {appslist:s} مجهولة.", "appslist_unknown": "قائمة التطبيقات {appslist:s} مجهولة.",
"appslist_url_already_tracked": "There is already a registered application list with url {url:s}.", "appslist_url_already_tracked": "There is already a registered application list with url {url:s}.",
"ask_current_admin_password": "كلمة السر الإدارية الحالية", "ask_current_admin_password": "كلمة السر الإدارية الحالية",
"ask_email": "عنوان البريد الإلكتروني", "ask_email": "عنوان البريد الإلكتروني",
@ -114,7 +114,7 @@
"certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)",
"certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}",
"certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !", "certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !",
"certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", "certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}!",
"certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !", "certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !",
"certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة", "certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة",
"certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…",
@ -232,7 +232,7 @@
"migrations_no_migrations_to_run": "No migrations to run", "migrations_no_migrations_to_run": "No migrations to run",
"migrations_show_currently_running_migration": "Running migration {number} {name}…", "migrations_show_currently_running_migration": "Running migration {number} {name}…",
"migrations_show_last_migration": "Last ran migration is {}", "migrations_show_last_migration": "Last ran migration is {}",
"migrations_skip_migration": "جارٍ تجاهل التهجير {number} {name}…", "migrations_skip_migration": "جارٍ تجاهل التهجير {id}…",
"monitor_disabled": "The server monitoring has been disabled", "monitor_disabled": "The server monitoring has been disabled",
"monitor_enabled": "The server monitoring has been enabled", "monitor_enabled": "The server monitoring has been enabled",
"monitor_glances_con_failed": "Unable to connect to Glances server", "monitor_glances_con_failed": "Unable to connect to Glances server",
@ -316,7 +316,7 @@
"service_conf_updated": "The configuration has been updated for service '{service}'", "service_conf_updated": "The configuration has been updated for service '{service}'",
"service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'",
"service_disable_failed": "", "service_disable_failed": "",
"service_disabled": "The service '{service:s}' has been disabled", "service_disabled": "تم تعطيل خدمة '{service:s}'",
"service_enable_failed": "", "service_enable_failed": "",
"service_enabled": "تم تنشيط خدمة '{service:s}'", "service_enabled": "تم تنشيط خدمة '{service:s}'",
"service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض", "service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض",
@ -347,7 +347,7 @@
"upgrade_complete": "إكتملت عملية الترقية و التحديث", "upgrade_complete": "إكتملت عملية الترقية و التحديث",
"upgrading_packages": "عملية ترقية الحُزم جارية …", "upgrading_packages": "عملية ترقية الحُزم جارية …",
"upnp_dev_not_found": "No UPnP device found", "upnp_dev_not_found": "No UPnP device found",
"upnp_disabled": "UPnP has been disabled", "upnp_disabled": "تم تعطيل UPnP",
"upnp_enabled": "UPnP has been enabled", "upnp_enabled": "UPnP has been enabled",
"upnp_port_open_failed": "Unable to open UPnP ports", "upnp_port_open_failed": "Unable to open UPnP ports",
"user_created": "تم إنشاء المستخدم", "user_created": "تم إنشاء المستخدم",
@ -441,5 +441,7 @@
"group_name_already_exist": "الفريق {name:s} موجود بالفعل", "group_name_already_exist": "الفريق {name:s} موجود بالفعل",
"error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers", "error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers",
"dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.", "dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.",
"backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…" "backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…",
"root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.",
"app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق"
} }

View file

@ -7,6 +7,7 @@
"admin_password_too_long": "Please choose a password shorter than 127 characters", "admin_password_too_long": "Please choose a password shorter than 127 characters",
"already_up_to_date": "Nothing to do. Everything is already up-to-date.", "already_up_to_date": "Nothing to do. Everything is already up-to-date.",
"app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down): {services}", "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down): {services}",
"app_action_broke_system": "This action seem to have broke these important services: {services}",
"app_already_installed": "{app:s} is already installed", "app_already_installed": "{app:s} is already installed",
"app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Look into `app changeurl` if it's available.",
"app_already_up_to_date": "{app:s} is already up-to-date", "app_already_up_to_date": "{app:s} is already up-to-date",
@ -28,7 +29,8 @@
"app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps:s}", "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps:s}",
"app_manifest_invalid": "Something is wrong with the app manifest: {error}", "app_manifest_invalid": "Something is wrong with the app manifest: {error}",
"app_no_upgrade": "All applications are already up-to-date", "app_no_upgrade": "All applications are already up-to-date",
"app_not_upgraded": "These apps were not upgraded: {apps}", "app_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps upgrades have been cancelled: {apps}",
"app_upgrade_stopped": "The upgrade of all applications has been stopped to prevent possible damage because the previous application failed to upgrade",
"app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed",
"app_not_installed": "Could not find the application '{app:s}' in the list of installed apps: {all_apps}", "app_not_installed": "Could not find the application '{app:s}' in the list of installed apps: {all_apps}",
"app_not_properly_removed": "{app:s} has not been properly removed", "app_not_properly_removed": "{app:s} has not been properly removed",
@ -407,6 +409,7 @@
"no_ipv6_connectivity": "IPv6 connectivity is not available", "no_ipv6_connectivity": "IPv6 connectivity is not available",
"no_restore_script": "No restore script found for the app '{app:s}'", "no_restore_script": "No restore script found for the app '{app:s}'",
"not_enough_disk_space": "Not enough free space on '{path:s}'", "not_enough_disk_space": "Not enough free space on '{path:s}'",
"operation_interrupted": "The operation was manually interrupted?",
"package_not_installed": "Package '{pkgname}' is not installed", "package_not_installed": "Package '{pkgname}' is not installed",
"package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'",
"package_unknown": "Unknown package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'",

View file

@ -16,15 +16,15 @@
"app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}", "app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}",
"app_no_upgrade": "No hay aplicaciones para actualizar", "app_no_upgrade": "No hay aplicaciones para actualizar",
"app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada", "app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada",
"app_not_installed": "{app:s} 9 no está instalada", "app_not_installed": "La aplicación «{app:s}» no está instalada. Esta es la lista de todas las aplicaciones instaladas: {all_apps}",
"app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente", "app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente",
"app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost", "app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost",
"app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ",
"app_removed": "{app:s} ha sido eliminada", "app_removed": "{app:s} ha sido eliminada",
"app_requirements_checking": "Comprobando los paquetes requeridos por {app}...", "app_requirements_checking": "Comprobando los paquetes necesarios para {app}…",
"app_requirements_failed": "No se cumplen los requisitos para {app}: {error}", "app_requirements_failed": "No se cumplen los requisitos para {app}: {error}",
"app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}", "app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}",
"app_sources_fetch_failed": "No se pudieron descargar los archivos del código fuente", "app_sources_fetch_failed": "No se pudo obtener los archivos con el código fuente, ¿es la URL correcta?",
"app_unknown": "Aplicación desconocida", "app_unknown": "Aplicación desconocida",
"app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación", "app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación",
"app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}", "app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}",
@ -50,11 +50,11 @@
"backup_archive_open_failed": "No se pudo abrir la copia de seguridad", "backup_archive_open_failed": "No se pudo abrir la copia de seguridad",
"backup_cleaning_failed": "No se puede limpiar el directorio temporal de copias de seguridad", "backup_cleaning_failed": "No se puede limpiar el directorio temporal de copias de seguridad",
"backup_created": "Se ha creado la copia de seguridad", "backup_created": "Se ha creado la copia de seguridad",
"backup_creating_archive": "Creando copia de seguridad...", "backup_creating_archive": "Creando el archivo de copia de seguridad…",
"backup_creation_failed": "No se pudo crear la copia de seguridad", "backup_creation_failed": "No se pudo crear la copia de seguridad",
"backup_delete_error": "No se puede eliminar '{path:s}'", "backup_delete_error": "No se puede eliminar '{path:s}'",
"backup_deleted": "La copia de seguridad ha sido eliminada", "backup_deleted": "La copia de seguridad ha sido eliminada",
"backup_extracting_archive": "Extrayendo la copia de seguridad...", "backup_extracting_archive": "Extrayendo el archivo de la copia de seguridad…",
"backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'", "backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'",
"backup_invalid_archive": "La copia de seguridad no es válida", "backup_invalid_archive": "La copia de seguridad no es válida",
"backup_nothings_done": "No hay nada que guardar", "backup_nothings_done": "No hay nada que guardar",
@ -86,21 +86,21 @@
"domain_zone_exists": "El archivo de zona del DNS ya existe", "domain_zone_exists": "El archivo de zona del DNS ya existe",
"domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]", "domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]",
"done": "Hecho.", "done": "Hecho.",
"downloading": "Descargando...", "downloading": "Descargando",
"dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada", "dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada",
"dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron DynDNS", "dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron de DynDNS por: {error}",
"dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada", "dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada",
"dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS",
"dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS", "dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS",
"dyndns_key_generating": "Se está generando la clave del DNS. Esto podría tardar unos minutos...", "dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato…",
"dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio", "dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio",
"dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS", "dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS",
"dyndns_registered": "El dominio DynDNS ha sido registrado", "dyndns_registered": "El dominio DynDNS ha sido registrado",
"dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}", "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}",
"dyndns_unavailable": "El dominio {domain:s} no está disponible.", "dyndns_unavailable": "El dominio {domain:s} no está disponible.",
"executing_command": "Ejecutando el comando '{command:s}'...", "executing_command": "Ejecutando la orden «{command:s}»…",
"executing_script": "Ejecutando el script '{script:s}'...", "executing_script": "Ejecutando el guión «{script:s}»…",
"extracting": "Extrayendo...", "extracting": "Extrayendo",
"field_invalid": "Campo no válido '{:s}'", "field_invalid": "Campo no válido '{:s}'",
"firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reload_failed": "No se pudo recargar el cortafuegos",
"firewall_reloaded": "El cortafuegos ha sido recargado", "firewall_reloaded": "El cortafuegos ha sido recargado",
@ -176,8 +176,8 @@
"restore_failed": "No se pudo restaurar el sistema", "restore_failed": "No se pudo restaurar el sistema",
"restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo", "restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo",
"restore_nothings_done": "No se ha restaurado nada", "restore_nothings_done": "No se ha restaurado nada",
"restore_running_app_script": "Ejecutando el script de restauración de la aplicación '{app:s}'...", "restore_running_app_script": "Ejecutando el guión de restauración de la aplicación «{app:s}»…",
"restore_running_hooks": "Ejecutando los hooks de restauración...", "restore_running_hooks": "Ejecutando los ganchos de restauración…",
"service_add_failed": "No se pudo añadir el servicio '{service:s}'", "service_add_failed": "No se pudo añadir el servicio '{service:s}'",
"service_added": "Servicio '{service:s}' ha sido añadido", "service_added": "Servicio '{service:s}' ha sido añadido",
"service_already_started": "El servicio '{service:s}' ya ha sido inicializado", "service_already_started": "El servicio '{service:s}' ya ha sido inicializado",
@ -220,9 +220,9 @@
"unlimit": "Sin cuota", "unlimit": "Sin cuota",
"unrestore_app": "La aplicación '{app:s}' no será restaurada", "unrestore_app": "La aplicación '{app:s}' no será restaurada",
"update_cache_failed": "No se pudo actualizar la caché de APT", "update_cache_failed": "No se pudo actualizar la caché de APT",
"updating_apt_cache": "Actualizando lista de paquetes disponibles...", "updating_apt_cache": "Obteniendo las actualizaciones disponibles para los paquetes del sistema…",
"upgrade_complete": "Actualización finalizada", "upgrade_complete": "Actualización finalizada",
"upgrading_packages": "Actualizando paquetes...", "upgrading_packages": "Actualizando paquetes",
"upnp_dev_not_found": "No se encontró ningún dispositivo UPnP", "upnp_dev_not_found": "No se encontró ningún dispositivo UPnP",
"upnp_disabled": "UPnP ha sido deshabilitado", "upnp_disabled": "UPnP ha sido deshabilitado",
"upnp_enabled": "UPnP ha sido habilitado", "upnp_enabled": "UPnP ha sido habilitado",
@ -239,7 +239,7 @@
"yunohost_already_installed": "YunoHost ya está instalado", "yunohost_already_installed": "YunoHost ya está instalado",
"yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad", "yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad",
"yunohost_configured": "YunoHost ha sido configurado", "yunohost_configured": "YunoHost ha sido configurado",
"yunohost_installing": "Instalando YunoHost...", "yunohost_installing": "Instalando YunoHost",
"yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'", "yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'",
"ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador", "ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador",
"mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo", "mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo",
@ -248,9 +248,9 @@
"certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)", "certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)",
"certmanager_domain_unknown": "Dominio desconocido {domain:s}", "certmanager_domain_unknown": "Dominio desconocido {domain:s}",
"certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)", "certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)",
"certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} ha fallado de alguna manera...", "certmanager_certificate_fetching_or_enabling_failed": "Suena como que habilitar el nuevo certificado para {domain:s} fallara de algún modo…",
"certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!", "certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!",
"certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} no está a punto de expirar! Utilice --force para omitir este mensaje", "certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio {domain:s} no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)",
"certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta", "certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta",
"certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", "certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)",
"certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)", "certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)",
@ -273,7 +273,7 @@
"certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.", "certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.",
"certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.", "certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.",
"appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido", "appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido",
"domain_hostname_failed": "Error al establecer nuevo nombre de host", "domain_hostname_failed": "Error al establecer un nuevo nombre de host («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).",
"yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.", "yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.",
"app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.", "app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.",
"app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.", "app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.",
@ -285,19 +285,19 @@
"app_already_up_to_date": "La aplicación {app:s} ya está actualizada", "app_already_up_to_date": "La aplicación {app:s} ya está actualizada",
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.", "appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.",
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.", "appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.",
"appslist_migrating": "Migrando la lista de aplicaciones {appslist:s} ...", "appslist_migrating": "Migrando la lista de aplicaciones {appslist:s}",
"appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.", "appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.",
"appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.", "appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.",
"invalid_url_format": "Formato de URL no válido", "invalid_url_format": "Formato de URL no válido",
"app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones", "app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones",
"app_make_default_location_already_used": "No puede hacer la aplicación '{app}' por defecto en el dominio {domain} dado que está siendo usado por otra aplicación '{other_app}'", "app_make_default_location_already_used": "No puede hacer la aplicación '{app}' por defecto en el dominio {domain} dado que está siendo usado por otra aplicación '{other_app}'",
"app_upgrade_app_name": "Actualizando la aplicación {app}...", "app_upgrade_app_name": "Actualizando la aplicación {app}",
"ask_path": "Camino", "ask_path": "Camino",
"backup_abstract_method": "Este método de backup no ha sido implementado aún", "backup_abstract_method": "Este método de backup no ha sido implementado aún",
"backup_applying_method_borg": "Enviando todos los ficheros al backup en el repositorio borg-backup...", "backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…",
"backup_applying_method_copy": "Copiado todos los ficheros al backup...", "backup_applying_method_copy": "Copiando todos los archivos a la copia de seguridad…",
"backup_applying_method_custom": "Llamando el método de backup {method:s} ...", "backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…",
"backup_applying_method_tar": "Creando el archivo tar de backup...", "backup_applying_method_tar": "Creando el archivo tar de la copia de seguridad…",
"backup_archive_mount_failed": "Fallo en el montado del archivo de backup", "backup_archive_mount_failed": "Fallo en el montado del archivo de backup",
"backup_archive_system_part_not_available": "La parte del sistema {part:s} no está disponible en este backup", "backup_archive_system_part_not_available": "La parte del sistema {part:s} no está disponible en este backup",
"backup_archive_writing_error": "No se pueden añadir archivos de backup en el archivo comprimido", "backup_archive_writing_error": "No se pueden añadir archivos de backup en el archivo comprimido",
@ -305,7 +305,7 @@
"backup_borg_not_implemented": "Método de backup Borg no está implementado aún", "backup_borg_not_implemented": "Método de backup Borg no está implementado aún",
"backup_cant_mount_uncompress_archive": "No se puede montar en modo solo lectura el directorio del archivo descomprimido", "backup_cant_mount_uncompress_archive": "No se puede montar en modo solo lectura el directorio del archivo descomprimido",
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo", "backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo",
"backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}", "backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}.",
"backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV", "backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV",
"backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración", "backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración",
"backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"", "backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"",
@ -323,5 +323,286 @@
"password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud",
"password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas", "password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas",
"password_too_simple_3": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales", "password_too_simple_3": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales",
"password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales" "password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales",
"users_available": "Usuarios disponibles:",
"user_not_in_group": "Usuario {user:s} no está en el grupo {group:s}",
"user_already_in_group": "Usuario {user:} ya está en el grupo {group:s}",
"updating_app_lists": "Obteniendo actualizaciones disponibles para las aplicaciones…",
"update_apt_cache_warning": "Ocurrieron algunos errores durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}",
"update_apt_cache_failed": "No se puede actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}",
"tools_upgrade_special_packages_completed": "¡Actualización de paquetes de YunoHost completada!\nPulse [Intro] para recuperar la línea de órdenes",
"tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez que esté hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas > Registro (en la administración web) o mediante «yunohost log list» (en la línea de órdenes).",
"tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)...",
"tools_upgrade_regular_packages_failed": "No se pueden actualizar los paquetes: {packages_list}",
"tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)...",
"tools_upgrade_cant_unhold_critical_packages": "No se pueden liberar los paquetes críticos...",
"tools_upgrade_cant_hold_critical_packages": "No se pueden retener los paquetes críticos...",
"tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo",
"tools_upgrade_at_least_one": "Especifique --apps O --system",
"tools_update_failed_to_app_fetchlist": "Error al actualizar la lista de aplicaciones de YunoHost porque: {error}",
"this_action_broke_dpkg": "Esta acción rompió dpkg/apt (los gestores de paquetes del sistema)... Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.",
"system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema",
"service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado",
"service_reload_or_restart_failed": "No se puede recargar o reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}",
"service_restarted": "El servicio «{service:s}» ha sido reiniciado",
"service_restart_failed": "No se puede reiniciar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}",
"service_reloaded": "El servicio «{service:s}» ha sido recargado",
"service_reload_failed": "No se puede recargar el servicio «{service:s}»'\n\nRegistro de servicios reciente:{logs:s}",
"service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.",
"service_description_yunohost-firewall": "gestiona los puertos de conexiones abiertos y cerrados a los servicios",
"service_description_yunohost-api": "gestiona las interacciones entre la interfaz web de YunoHost y el sistema",
"service_description_ssh": "le permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)",
"service_description_slapd": "almacena usuarios, dominios e información relacionada",
"service_description_rspamd": "filtra correo no deseado y otras características relacionadas con el correo",
"service_description_rmilter": "comprueba varios parámetros en el correo",
"service_description_redis-server": "una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas",
"service_description_postfix": "usado para enviar y recibir correos",
"service_description_php7.0-fpm": "ejecuta aplicaciones escritas en PHP con nginx",
"service_description_nslcd": "maneja la conexión del intérprete («shell») de usuario de YunoHost",
"service_description_nginx": "sirve o proporciona acceso a todos los sitios web alojados en su servidor",
"service_description_mysql": "almacena los datos de las aplicaciones (base de datos SQL)",
"service_description_metronome": "gestionar las cuentas XMPP de mensajería instantánea",
"service_description_glances": "supervisa la información del sistema en su servidor",
"service_description_fail2ban": "protege contra ataques de fuerza bruta y otra clase de ataques desde Internet",
"service_description_dovecot": "permite al cliente de correo acceder/traer correo (vía IMAP y POP3)",
"service_description_dnsmasq": "maneja la resolución de nombres de dominio (DNS)",
"service_description_avahi-daemon": "permite acceder a su servidor usando yunohost.local en su red local",
"server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]",
"server_reboot": "El servidor se reiniciará",
"server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]",
"server_shutdown": "El servidor se apagará",
"root_password_replaced_by_admin_password": "Su contraseña de root ha sido sustituida por su contraseña de administración.",
"root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto en la contraseña de root!",
"restore_system_part_failed": "No se puede restaurar la parte del sistema «{part:s}»",
"restore_removing_tmp_dir_failed": "No se puede eliminar un antiguo directorio temporal",
"restore_not_enough_disk_space": "Insuficiente espacio en disco (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)",
"restore_mounting_archive": "Montando archivo en «{path:s}»",
"restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio de disco libre (espacio libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)",
"restore_extracting": "Extrayendo los archivos necesarios para el archivo…",
"regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…",
"regenconf_failed": "No se puede regenerar la configuración para la(s) categoría(s): {categories}",
"regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…",
"regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»",
"regenconf_updated": "Ha sido actualizada la configuración para la categoría «{category}»",
"regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»",
"regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).",
"regenconf_file_updated": "El archivo de configuración «{conf}» ha sido actualizado",
"regenconf_file_removed": "El archivo de configuración «{conf}» ha sido eliminado",
"regenconf_file_remove_failed": "No se puede eliminar el archivo de configuración «{conf}»",
"regenconf_file_manually_removed": "El archivo de configuración «{conf}» ha sido eliminado manualmente y no se creará",
"regenconf_file_manually_modified": "El archivo de configuración «{conf}» ha sido modificado manualmente y no será actualizado",
"regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.",
"regenconf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración «{new}» a «{conf}»",
"regenconf_file_backed_up": "El archivo de configuración «{conf}» ha sido respaldado en «{backup}»",
"remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario {user:s} en el grupo {group:s}",
"remove_main_permission_not_allowed": "No se permite eliminar el permiso principal",
"recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create <nombredeusuario>» o usando la interfaz de administración.",
"permission_update_nothing_to_do": "No hay permisos para actualizar",
"permission_updated": "Permiso «{permission:s}» para la aplicación {app:s} actualizado",
"permission_generated": "La base de datos de permisos se ha actualizado",
"permission_update_failed": "Actualización de permiso fallida",
"permission_name_not_valid": "Nombre de permiso «{permission:s}» no válido",
"permission_not_found": "Permiso «{permission:s}» no encontrado para la aplicación {app:s}",
"permission_deletion_failed": "Permiso «{permission:s}» para eliminar la aplicación «{app:s}» fallido",
"permission_deleted": "Eliminado el permiso «{permission:s}» para la aplicación {app:s}",
"permission_creation_failed": "Ha fallado la creación del permiso",
"permission_created": "Creado el permiso «{permission:s}» para la aplicación {app:s}",
"permission_already_exist": "El permiso «{permission:s}» para la aplicación {app:s} ya existe",
"permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}",
"pattern_password_app": "Las contraseñas no deben incluir los siguientes caracteres: {forbidden_chars}",
"need_define_permission_before": "Necesita redefinir los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido",
"migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas > Migraciones en la web de administración o ejecute `yunohost tools migrations migrate`.",
"migrations_success_forward": "¡Migración {id} ejecutada correctamente!",
"migrations_skip_migration": "Omitiendo migración {id}…",
"migrations_running_forward": "Ejecutando migración {id}…",
"migrations_pending_cant_rerun": "Esas migraciones están aún pendientes así que no se pueden volver a ejecutar: {ids}",
"migrations_not_pending_cant_skip": "Esas migraciones no están pendientes así que no pueden ser omitidas: {ids}",
"migrations_no_such_migration": "No existe una migración llamada {id}",
"migrations_no_migrations_to_run": "No hay migraciones que ejecutar",
"migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción --accept-disclaimer.",
"migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar --skip o --force-rerun",
"migrations_migration_has_failed": "Migración {id} fallida, cancelando. Error: {exception}",
"migrations_loading_migration": "Cargando migración {id}…",
"migrations_list_conflict_pending_done": "No puede usar --previous y --done al mismo tiempo.",
"migrations_exclusive_options": "--auto, --skip, and --force-rerun son opciones excluyentes.",
"migrations_failed_to_load_migration": "Error al cargar la migración {id} : {error}",
"migrations_dependencies_not_satisfied": "No se puede ejecutar la migración {id} porque primero necesita ejecutar estas migraciones: {dependencies_id}",
"migrations_cant_reach_migration_file": "No se pueden acceder los archivos de migración en la ruta %s",
"migrations_already_ran": "Esas migraciones ya se han ejecutado: {ids}",
"migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP...",
"migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP...",
"migration_0011_rollback_success": "Revertido correctamente.",
"migration_0011_migration_failed_trying_to_rollback": "Migración fallida... intentando revertir el sistema.",
"migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP...",
"migration_0011_LDAP_update_failed": "Actualización de LDAP fallida. Error: {error:s}",
"migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, restaurar la configuración original con la orden «yunohost tools regen-conf -f» y reintentar después la migración",
"migration_0011_done": "Migración exitosa. Ahora puede gestionar los grupos de usuarios.",
"migration_0011_create_group": "Creando un grupo para cada usuario...",
"migration_0011_can_not_backup_before_migration": "Falló la copia de seguridad del sistema antes de la migración. Migración fallida. Error: {error:s}",
"migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.",
"migration_0009_not_needed": "¿La migración ya ocurrió de algún modo? Omitiendo.",
"migration_0008_no_warning": "No se ha detectado ningún riesgo importante sobre la anulación de su configuración SSH ¡pero no existe una certeza absoluta ;)! Si permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.",
"migration_0008_warning": "Si entiende esos avisos y permite a YunoHost anular su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.",
"migration_0008_dsa": " - se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;",
"migration_0008_root": " - no podrá conectarse como «root» a través de SSH. En su lugar debería usar el usuario de administración;",
"migration_0008_port": " - tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;",
"migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración SSH. Su actual configuración SSH difiere de la configuración recomendada. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará en el siguiente modo:",
"migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.",
"migration_0007_cancelled": "YunoHost no ha podido mejorar el modo en el que se gestiona su configuración de SSH.",
"migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Al ejecutar esta migración, su contraseña de «root» será reemplazada por la contraseña de administración.",
"migration_0005_not_enough_space": "No hay suficiente espacio libre disponible en {path} para ejecutar la migración en este momento:(.",
"migration_0005_postgresql_96_not_installed": "¿¡Se encontró postgresql 9.4 para ser instalado pero no postgresql 9.6!? Algo raro podría haber ocurrido en su sistema:(…",
"migration_0005_postgresql_94_not_installed": "Postgresql no estaba instalado en su sistema. ¡Nada que hacer!",
"migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos al final de la actualización: {manually_modified_files}",
"migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como «funciona». Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}",
"migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. Aunque el equipo de YunoHost hizo todo lo posible para revisarla y probarla, la migración aún podría romper parte del sistema o de las aplicaciones.\n\nPor lo tanto le recomendamos que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto 465 se cerrará automáticamente y el nuevo puerto 587 se abrirá en el cortafuegos. ¡Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto!",
"migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ¿¡el sistema está aún en Jessie!? Para investigar el problema, vea {log}:s…",
"migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.",
"migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!",
"migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete «yunohost»… La migración finalizará pero la actualización real ocurrirá justo después. Después de que la operación esté completada, podría tener que reiniciar sesión en la administración web.",
"migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado de algún modo. La migración lo devolverá a su estado original primero… El archivo anterior estará disponible como {backup_dest}.",
"migration_0003_fail2ban_upgrade": "Iniciando la actualización de «fail2ban»…",
"migration_0003_main_upgrade": "Iniciando la actualización principal…",
"migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…",
"migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.",
"migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de postgresql a usar md5 para las conexiones locales",
"migration_description_0011_setup_group_permission": "Configurar grupo de usuario y configurar permisos para aplicaciones y servicios",
"migration_description_0010_migrate_to_apps_json": "Eliminar la obsoleta «appslists» y usar la nueva lista unificada «apps.json»",
"migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios",
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)",
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)",
"migration_description_0006_sync_admin_and_root_passwords": "Sincronizar las contraseñas de «admin» y «root»",
"migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de postgresql 9.4 a 9.6",
"migration_description_0004_php5_to_php7_pools": "Reconfigurar los «pools» de PHP para usar PHP 7 en vez de 5",
"migration_description_0003_migrate_to_stretch": "Actualizar el sistema a Debian Stretch y YunoHost 3.0",
"migration_description_0002_migrate_to_tsig_sha256": "Mejorar la seguridad de la TSIG de dyndns usando SHA512 en vez de MD5",
"migration_description_0001_change_cert_group_to_sslcert": "Cambiar los permisos de grupo de certificados de «metronome» a «ssl-cert»",
"migrate_tsig_not_needed": "Parece que no usa un dominio dyndns ¡así que no es necesario migrar!",
"migrate_tsig_wait_4": "30 segundos…",
"migrate_tsig_wait_3": "1 min. …",
"migrate_tsig_wait_2": "2 min. …",
"migrate_tsig_wait": "Esperar 3 min. para que el servidor dyndns tenga en cuenta la nueva clave…",
"migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro hmac-sha512",
"migrate_tsig_failed": "Error al migrar el dominio de dyndns {domain} a hmac-sha512, revertiendo. Error: {error_code} - {error}",
"migrate_tsig_end": "Terminada la migración a hmac-sha512",
"mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario",
"mailbox_disabled": "Mailbox desactivado para usuario {user:s}",
"log_tools_reboot": "Reiniciar el servidor",
"log_tools_shutdown": "Apagar el servidor",
"log_tools_upgrade": "Actualizar paquetes del sistema",
"log_tools_postinstall": "Posinstalación del servidor YunoHost",
"log_tools_migrations_migrate_forward": "Migrar hacia adelante",
"log_tools_maindomain": "Convertir «{}» en dominio principal",
"log_user_permission_remove": "Actualizar permiso «{}»",
"log_user_permission_add": "Actualizar permiso «{}»",
"log_user_update": "Actualizar información del usuario «{}»",
"log_user_group_update": "Actualizar grupo «{}»",
"log_user_group_delete": "Eliminar grupo «{}»",
"log_user_group_add": "Añadir grupo «{}»",
"log_user_delete": "Eliminar usuario «{}»",
"log_user_create": "Añadir usuario «{}»",
"log_regen_conf": "Regenerar la configuración del sistema «{}»",
"log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's encrypt",
"log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»",
"log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»",
"log_permission_remove": "Eliminar permiso «{}»",
"log_permission_add": "Añadir permiso «{}» para la aplicación «{}»",
"log_letsencrypt_cert_install": "Instalar certificado de Let's encrypt en el dominio «{}»",
"log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»",
"log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»",
"log_domain_remove": "Eliminar el dominio «{}» de la configuración del sistema",
"log_domain_add": "Añadir el dominio «{}» a la configuración del sistema",
"log_remove_on_failed_install": "Eliminar «{}» después de una instalación fallida",
"log_remove_on_failed_restore": "Eliminar «{}» después de una restauración fallida desde un archivo de respaldo",
"log_backup_restore_app": "Restaurar «{}» desde un archivo de respaldo",
"log_backup_restore_system": "Restaurar sistema desde un archivo de respaldo",
"log_available_on_yunopaste": "Este registro está ahora disponible vía {url}",
"log_app_makedefault": "Convertir «{}» en aplicación predeterminada",
"log_app_upgrade": "Actualizar la aplicación «{}»",
"log_app_remove": "Eliminar la aplicación «{}»",
"log_app_install": "Instalar la aplicación «{}»",
"log_app_change_url": "Cambiar la url de la aplicación «{}»",
"log_app_removelist": "Eliminar una lista de aplicaciones",
"log_app_fetchlist": "Añadir una lista de aplicaciones",
"log_app_clearaccess": "Eliminar todos los accesos a «{}»",
"log_app_removeaccess": "Eliminar acceso a «{}»",
"log_app_addaccess": "Añadir acceso a «{}»",
"log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente",
"log_does_exists": "No existe ningún registro de actividades con el nombre «{log}», ejecute «yunohost log list» para ver todos los registros de actividades disponibles",
"log_help_to_get_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»",
"log_link_to_failed_log": "¡La operación «{desc}» ha fallado! Para obtener ayuda, <a href=\"#/tools/logs/{name}\">proporcione el registro completo de esta operación pulsando aquí</a>",
"log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log display {name}»",
"log_link_to_log": "Registro completo de esta operación: «<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>»",
"log_category_404": "La categoría de registro «{category}» no existe",
"log_corrupted_md_file": "El archivo de metadatos yaml asociado con el registro está dañado: «{md_file}\nError: {error}»",
"hook_json_return_error": "Error al leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}",
"group_update_failed": "Error en la actualización del grupo «{group}»",
"group_updated": "Grupo «{group}» actualizado",
"group_unknown": "Grupo {group:s} desconocido",
"group_info_failed": "Error en la información del grupo",
"group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.",
"group_deletion_failed": "Error al eliminar el grupo «{group}»",
"group_deleted": "Eliminado el grupo «{group}»",
"group_creation_failed": "Error al crear el grupo «{group}»",
"group_created": "Grupo «{group}» creado correctamente",
"group_name_already_exist": "El grupo {name:s} ya existe",
"group_already_disallowed": "El grupo '{group:s}' ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»",
"group_already_allowed": "El grupo '{group:s}' ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»",
"good_practices_about_admin_password": "Va a determinar una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar contraseñas más extensas (esto es, una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).",
"global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} pero no es un tipo compatible con el sistema.",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH",
"global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key:s}», desechada y guardada en /etc/yunohost/settings-unknown.json",
"global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor Postfix. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario",
"global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador",
"global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web nginx. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_example_string": "Ejemplo de opción de cadena",
"global_settings_setting_example_int": "Ejemplo de opción «int»",
"global_settings_setting_example_enum": "Ejemplo de opción «enum»",
"global_settings_setting_example_bool": "Ejemplo de opción booleana",
"global_settings_reset_success": "Éxito. Se ha respaldado su configuración previa en {path:s}",
"global_settings_key_doesnt_exists": "La clave «{settings_key:s}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»",
"global_settings_cant_write_settings": "Error al escribir el archivo de configuración, motivo: {reason:s}",
"global_settings_cant_serialize_settings": "Error al seriar los datos de configuración, motivo: {reason:s}",
"global_settings_cant_open_settings": "Error al abrir el archivo de configuración, motivo: {reason:s}",
"global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, excepto {expected_type:s}",
"global_settings_bad_choice_for_enum": "Mala elección para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}",
"file_does_not_exist": "El archivo {path:s} no existe.",
"error_when_removing_sftpuser_group": "Error al probar «remove sftpusers group»",
"edit_permission_with_group_all_users_not_allowed": "No puede editar el permiso para el grupo «all_users», utilice «yunohost user permission clear APLICACIÓN» o «yunohost user permission add APLICACIÓN -u USUARIO».",
"edit_group_not_allowed": "No tiene permiso para editar el grupo {group:s}",
"dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.",
"domain_dyndns_dynette_is_unreachable": "No se pudo conectar al dynette de YunoHost, o su YunoHost no está correctamente conectado a internet o el servidor dynette está caído. Error: {error}",
"domain_dns_conf_is_just_a_recommendation": "Esta orden muestra cuál es la configuración *recomendada*. No configura el DNS. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.",
"dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)",
"dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.",
"confirm_app_install_thirdparty": "¡AVISO! Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarlas salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ",
"confirm_app_install_danger": "¡AVISO! Esta aplicación es aún experimental (si no está funcionando expresamente) y ¡es probable que rompa su sistema! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. ¿Está dispuesto a correr ese riesgo? [{answers:s}] ",
"confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ",
"backup_unable_to_organize_files": "No se pueden organizar los archivos en el archivo con el método rápido",
"backup_permission": "Permiso de respaldo para la aplicación {app:s}",
"backup_output_symlink_dir_broken": "Tiene un enlace simbólico roto en vez del directorio «{path:s}» de sus archivos. Puede que tenga una configuración específica para respaldar sus datos en otro sistema de archivos, en este caso probablemente olvidó remontar o conectar su disco duro o clave usb.",
"backup_mount_archive_for_restore": "Preparando el archivo para la restauración…",
"backup_method_tar_finished": "Creado el archivo de respaldo tar",
"backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado",
"backup_method_copy_finished": "Terminada la copia de seguridad",
"backup_method_borg_finished": "Terminado el respaldo en borg",
"backup_custom_backup_error": "Fallo del método de respaldo personalizado en el paso «copia de seguridad»",
"backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…",
"ask_new_path": "Nueva ruta",
"ask_new_domain": "Nuevo dominio",
"apps_permission_restoration_failed": "El permiso «{permission:s}» para la restauración de la aplicación {app:s} ha fallado",
"apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas",
"app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}",
"app_start_restore": "Restaurando aplicación {app}…",
"app_start_backup": "Obteniendo archivos de respaldo para {app}…",
"app_start_remove": "Eliminando aplicación {app}…",
"app_start_install": "Instalando aplicación {app}…",
"app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}",
"app_action_cannot_be_ran_because_required_services_down": "Esta aplicación necesita algunos servicios que no están funcionando ahora. Antes de continuar, debería intentar reiniciar los siguientes servicios (y posiblemente investigar por qué no funcionan): {services}",
"already_up_to_date": "¡Nada que hacer! ¡Todo está actualizado!",
"admin_password_too_long": "Elija una contraseña de menos de 127 caracteres",
"aborting": "Cancelando.",
"app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque la aplicación anterior no se pudo actualizar"
} }

View file

@ -584,15 +584,9 @@ def app_upgrade(app=[], url=None, file=None):
url -- Git url to fetch for upgrade url -- Git url to fetch for upgrade
""" """
if packages.dpkg_is_broken():
raise YunohostError("dpkg_is_broken")
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.permission import permission_sync_to_user from yunohost.permission import permission_sync_to_user
# Retrieve interface
is_api = msettings.get('interface') == 'api'
try: try:
app_list() app_list()
except YunohostError: except YunohostError:
@ -621,12 +615,15 @@ def app_upgrade(app=[], url=None, file=None):
if len(apps) > 1: if len(apps) > 1:
logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps))) logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps)))
for app_instance_name in apps: for number, app_instance_name in enumerate(apps):
logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name))
app_dict = app_info(app_instance_name, raw=True) app_dict = app_info(app_instance_name, raw=True)
if file: if file and isinstance(file, dict):
# We use this dirty hack to test chained upgrades in unit/functional tests
manifest, extracted_app_folder = _extract_app_from_file(file[app_instance_name])
elif file:
manifest, extracted_app_folder = _extract_app_from_file(file) manifest, extracted_app_folder = _extract_app_from_file(file)
elif url: elif url:
manifest, extracted_app_folder = _fetch_app_from_git(url) manifest, extracted_app_folder = _fetch_app_from_git(url)
@ -641,7 +638,7 @@ def app_upgrade(app=[], url=None, file=None):
# Check requirements # Check requirements
_check_manifest_requirements(manifest, app_instance_name=app_instance_name) _check_manifest_requirements(manifest, app_instance_name=app_instance_name)
_check_services_status_for_app(manifest.get("services", [])) _assert_system_is_sane_for_app(manifest, "pre")
app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name
@ -672,48 +669,83 @@ def app_upgrade(app=[], url=None, file=None):
# Execute App upgrade script # Execute App upgrade script
os.system('chown -hR admin: %s' % INSTALL_TMP) os.system('chown -hR admin: %s' % INSTALL_TMP)
if hook_exec(extracted_app_folder + '/scripts/upgrade',
args=args_list, env=env_dict)[0] != 0:
msg = m18n.n('app_upgrade_failed', app=app_instance_name)
not_upgraded_apps.append(app_instance_name)
logger.error(msg)
operation_logger.error(msg)
else:
now = int(time.time())
# TODO: Move install_time away from app_setting
app_setting(app_instance_name, 'update_time', now)
status['upgraded_at'] = now
# Clean hooks and add new ones try:
hook_remove(app_instance_name) upgrade_retcode = hook_exec(extracted_app_folder + '/scripts/upgrade',
if 'hooks' in os.listdir(extracted_app_folder): args=args_list, env=env_dict)[0]
for hook in os.listdir(extracted_app_folder + '/hooks'): except (KeyboardInterrupt, EOFError):
hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook) upgrade_retcode = -1
except Exception:
import traceback
logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
finally:
# Store app status # Did the script succeed ?
with open(app_setting_path + '/status.json', 'w+') as f: if upgrade_retcode == -1:
json.dump(status, f) error_msg = m18n.n('operation_interrupted')
operation_logger.error(error_msg)
elif upgrade_retcode != 0:
error_msg = m18n.n('app_upgrade_failed', app=app_instance_name)
operation_logger.error(error_msg)
# Replace scripts and manifest and conf (if exists) # Did it broke the system ?
os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path)) try:
broke_the_system = False
_assert_system_is_sane_for_app(manifest, "post")
except Exception as e:
broke_the_system = True
error_msg = operation_logger.error(str(e))
if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): # If upgrade failed or broke the system,
os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) # raise an error and interrupt all other pending upgrades
if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): if upgrade_retcode != 0 or broke_the_system:
os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path))
for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]: # display this if there are remaining apps
if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): if apps[number + 1:]:
os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) logger.error(m18n.n('app_upgrade_stopped'))
not_upgraded_apps = apps[number:]
# we don't want to continue upgrading apps here in case that breaks
# everything
raise YunohostError('app_not_upgraded',
failed_app=app_instance_name,
apps=', '.join(not_upgraded_apps))
else:
raise YunohostError(error_msg, raw_msg=True)
# So much win # Otherwise we're good and keep going !
logger.success(m18n.n('app_upgraded', app=app_instance_name)) else:
now = int(time.time())
# TODO: Move install_time away from app_setting
app_setting(app_instance_name, 'update_time', now)
status['upgraded_at'] = now
hook_callback('post_app_upgrade', args=args_list, env=env_dict) # Clean hooks and add new ones
operation_logger.success() hook_remove(app_instance_name)
if 'hooks' in os.listdir(extracted_app_folder):
for hook in os.listdir(extracted_app_folder + '/hooks'):
hook_add(app_instance_name, extracted_app_folder + '/hooks/' + hook)
if not_upgraded_apps: # Store app status
raise YunohostError('app_not_upgraded', apps=', '.join(not_upgraded_apps)) with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
# Replace scripts and manifest and conf (if exists)
os.system('rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path, app_setting_path))
if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")):
os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path))
if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")):
os.system('mv "%s/manifest.toml" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path))
for file_to_copy in ["actions.json", "actions.toml", "config_panel.json", "config_panel.toml", "conf"]:
if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)):
os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path))
# So much win
logger.success(m18n.n('app_upgraded', app=app_instance_name))
hook_callback('post_app_upgrade', args=args_list, env=env_dict)
operation_logger.success()
permission_sync_to_user() permission_sync_to_user()
@ -732,8 +764,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
force -- Do not ask for confirmation when installing experimental / low-quality apps force -- Do not ask for confirmation when installing experimental / low-quality apps
""" """
if packages.dpkg_is_broken():
raise YunohostError("dpkg_is_broken")
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
@ -797,7 +827,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
# Check requirements # Check requirements
_check_manifest_requirements(manifest, app_id) _check_manifest_requirements(manifest, app_id)
_check_services_status_for_app(manifest.get("services", [])) _assert_system_is_sane_for_app(manifest, "pre")
# Check if app can be forked # Check if app can be forked
instance_number = _installed_instance_number(app_id, last=True) + 1 instance_number = _installed_instance_number(app_id, last=True) + 1
@ -890,8 +920,17 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
import traceback import traceback
logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
finally: finally:
try:
broke_the_system = False
_assert_system_is_sane_for_app(manifest, "post")
except Exception as e:
broke_the_system = True
error_msg = operation_logger.error(str(e))
if install_retcode != 0: if install_retcode != 0:
error_msg = operation_logger.error(m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode)) error_msg = operation_logger.error(m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode))
if install_retcode != 0 or broke_the_system:
if not no_remove_on_failure: if not no_remove_on_failure:
# Setup environment for remove script # Setup environment for remove script
env_dict_remove = {} env_dict_remove = {}
@ -922,7 +961,12 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
logger.warning(msg) logger.warning(msg)
operation_logger_remove.error(msg) operation_logger_remove.error(msg)
else: else:
operation_logger_remove.success() try:
_assert_system_is_sane_for_app(manifest, "post")
except Exception as e:
operation_logger_remove.error(e)
else:
operation_logger_remove.success()
# Clean tmp folders # Clean tmp folders
shutil.rmtree(app_setting_path) shutil.rmtree(app_setting_path)
@ -930,9 +974,6 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
app_ssowatconf() app_ssowatconf()
if packages.dpkg_is_broken():
logger.error(m18n.n("this_action_broke_dpkg"))
if install_retcode == -1: if install_retcode == -1:
msg = m18n.n('operation_interrupted') + " " + error_msg msg = m18n.n('operation_interrupted') + " " + error_msg
raise YunohostError(msg, raw_msg=True) raise YunohostError(msg, raw_msg=True)
@ -1000,6 +1041,8 @@ def app_remove(operation_logger, app):
# script might date back from jessie install) # script might date back from jessie install)
_patch_php5(app_setting_path) _patch_php5(app_setting_path)
manifest = _get_manifest_of_app(app_setting_path)
os.system('cp -a %s /tmp/yunohost_remove && chown -hR admin: /tmp/yunohost_remove' % app_setting_path) os.system('cp -a %s /tmp/yunohost_remove && chown -hR admin: /tmp/yunohost_remove' % app_setting_path)
os.system('chown -R admin: /tmp/yunohost_remove') os.system('chown -R admin: /tmp/yunohost_remove')
os.system('chmod -R u+rX /tmp/yunohost_remove') os.system('chmod -R u+rX /tmp/yunohost_remove')
@ -1034,9 +1077,7 @@ def app_remove(operation_logger, app):
permission_remove(app, l.split('.')[0], force=True, sync_perm=False) permission_remove(app, l.split('.')[0], force=True, sync_perm=False)
permission_sync_to_user() permission_sync_to_user()
_assert_system_is_sane_for_app(manifest, "post")
if packages.dpkg_is_broken():
raise YunohostError("this_action_broke_dpkg")
@is_unit_operation(['permission','app']) @is_unit_operation(['permission','app'])
@ -2906,10 +2947,12 @@ def unstable_apps():
return output return output
def _check_services_status_for_app(services): def _assert_system_is_sane_for_app(manifest, when):
logger.debug("Checking that required services are up and running...") logger.debug("Checking that required services are up and running...")
services = manifest.get("services", [])
# Some apps use php-fpm or php5-fpm which is now php7.0-fpm # Some apps use php-fpm or php5-fpm which is now php7.0-fpm
def replace_alias(service): def replace_alias(service):
if service in ["php-fpm", "php5-fpm"]: if service in ["php-fpm", "php5-fpm"]:
@ -2924,11 +2967,26 @@ def _check_services_status_for_app(services):
service_filter = ["nginx", "php7.0-fpm", "mysql", "postfix"] service_filter = ["nginx", "php7.0-fpm", "mysql", "postfix"]
services = [str(s) for s in services if s in service_filter] services = [str(s) for s in services if s in service_filter]
if "nginx" not in services:
services = ["nginx"] + services
if "fail2ban" not in services:
services.append("fail2ban")
# List services currently down and raise an exception if any are found # List services currently down and raise an exception if any are found
faulty_services = [s for s in services if service_status(s)["active"] != "active"] faulty_services = [s for s in services if service_status(s)["active"] != "active"]
if faulty_services: if faulty_services:
raise YunohostError('app_action_cannot_be_ran_because_required_services_down', if when == "pre":
services=', '.join(faulty_services)) raise YunohostError('app_action_cannot_be_ran_because_required_services_down',
services=', '.join(faulty_services))
elif when == "post":
raise YunohostError('app_action_broke_system',
services=', '.join(faulty_services))
if packages.dpkg_is_broken():
if when == "pre":
raise YunohostError("dpkg_is_broken")
elif when == "post":
raise YunohostError("this_action_broke_dpkg")
def _patch_php5(app_folder): def _patch_php5(app_folder):

View file

@ -41,11 +41,11 @@ def pytest_cmdline_main(config):
root_handlers = set(handlers) root_handlers = set(handlers)
# Define loggers level # Define loggers level
level = 'INFO' level = 'DEBUG'
if config.option.yunodebug: if config.option.yunodebug:
tty_level = 'DEBUG' tty_level = 'DEBUG'
else: else:
tty_level = 'SUCCESS' tty_level = 'INFO'
# Custom logging configuration # Custom logging configuration
logging = { logging = {

View file

@ -0,0 +1,321 @@
import glob
import os
import pytest
import shutil
import requests
from moulinette import m18n
from moulinette.utils.filesystem import mkdir
from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade
from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list
from yunohost.utils.error import YunohostError
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
def setup_function(function):
clean()
def teardown_function(function):
clean()
def clean():
# Make sure we have a ssowat
os.system("mkdir -p /etc/ssowat/")
app_ssowatconf()
# Gotta first remove break yo system
# because some remaining stuff might
# make the other app_remove crashs ;P
if _is_installed("break_yo_system"):
app_remove("break_yo_system")
if _is_installed("legacy_app"):
app_remove("legacy_app")
to_remove = []
to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*")
to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*")
for filepath in to_remove:
os.remove(filepath)
to_remove = []
to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*")
to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*")
to_remove += glob.glob("/var/www/*legacy*")
for folderpath in to_remove:
shutil.rmtree(folderpath, ignore_errors=True)
os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ?
os.system("systemctl start nginx")
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
check_LDAP_db_integrity()
yield
check_LDAP_db_integrity()
@pytest.fixture(autouse=True)
def check_permission_for_apps_call():
check_permission_for_apps()
yield
check_permission_for_apps()
@pytest.fixture(scope="session")
def secondary_domain(request):
if "example.test" not in domain_list()["domains"]:
domain_add("example.test")
def remove_example_domain():
domain_remove("example.test")
request.addfinalizer(remove_example_domain)
return "example.test"
#
# Helpers #
#
def app_expected_files(domain, app):
yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app)
if app.startswith("legacy_app"):
yield "/var/www/%s/index.html" % app
yield "/etc/yunohost/apps/%s/settings.yml" % app
yield "/etc/yunohost/apps/%s/manifest.json" % app
yield "/etc/yunohost/apps/%s/scripts/install" % app
yield "/etc/yunohost/apps/%s/scripts/remove" % app
def app_is_installed(domain, app):
return _is_installed(app) and all(os.path.exists(f) for f in app_expected_files(domain, app))
def app_is_not_installed(domain, app):
return not _is_installed(app) and not all(os.path.exists(f) for f in app_expected_files(domain, app))
def app_is_exposed_on_http(domain, path, message_in_page):
try:
r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10)
return r.status_code == 200 and message_in_page in r.text
except Exception:
return False
def install_legacy_app(domain, path):
app_install("./tests/apps/legacy_app_ynh",
args="domain=%s&path=%s" % (domain, path),
force=True)
def install_break_yo_system(domain, breakwhat):
app_install("./tests/apps/break_yo_system_ynh",
args="domain=%s&breakwhat=%s" % (domain, breakwhat),
force=True)
def test_legacy_app_install_main_domain():
main_domain = _get_maindomain()
install_legacy_app(main_domain, "/legacy")
assert app_is_installed(main_domain, "legacy_app")
assert app_is_exposed_on_http(main_domain, "/legacy", "This is a dummy app")
app_remove("legacy_app")
assert app_is_not_installed(main_domain, "legacy_app")
def test_legacy_app_install_secondary_domain(secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
assert app_is_installed(secondary_domain, "legacy_app")
assert app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app")
app_remove("legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
install_legacy_app(secondary_domain, "/")
assert app_is_installed(secondary_domain, "legacy_app")
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
app_remove("legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_install_private(secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
settings = open("/etc/yunohost/apps/legacy_app/settings.yml", "r").read()
new_settings = settings.replace("\nunprotected_uris: /", "")
assert new_settings != settings
open("/etc/yunohost/apps/legacy_app/settings.yml", "w").write(new_settings)
app_ssowatconf()
assert app_is_installed(secondary_domain, "legacy_app")
assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app")
app_remove("legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_install_unknown_domain():
with pytest.raises(YunohostError):
install_legacy_app("whatever.nope", "/legacy")
# TODO check error message
assert app_is_not_installed("whatever.nope", "legacy_app")
def test_legacy_app_install_multiple_instances(secondary_domain):
install_legacy_app(secondary_domain, "/foo")
install_legacy_app(secondary_domain, "/bar")
assert app_is_installed(secondary_domain, "legacy_app")
assert app_is_exposed_on_http(secondary_domain, "/foo", "This is a dummy app")
assert app_is_installed(secondary_domain, "legacy_app__2")
assert app_is_exposed_on_http(secondary_domain, "/bar", "This is a dummy app")
app_remove("legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app")
assert app_is_installed(secondary_domain, "legacy_app__2")
app_remove("legacy_app__2")
assert app_is_not_installed(secondary_domain, "legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app__2")
def test_legacy_app_install_path_unavailable(secondary_domain):
# These will be removed in teardown
install_legacy_app(secondary_domain, "/legacy")
with pytest.raises(YunohostError):
install_legacy_app(secondary_domain, "/")
# TODO check error message
assert app_is_installed(secondary_domain, "legacy_app")
assert app_is_not_installed(secondary_domain, "legacy_app__2")
def test_legacy_app_install_bad_args():
with pytest.raises(YunohostError):
install_legacy_app("this.domain.does.not.exists", "/legacy")
def test_legacy_app_install_with_nginx_down(secondary_domain):
os.system("systemctl stop nginx")
with pytest.raises(YunohostError):
install_legacy_app(secondary_domain, "/legacy")
def test_legacy_app_failed_install(secondary_domain):
# This will conflict with the folder that the app
# attempts to create, making the install fail
mkdir("/var/www/legacy_app/", 0o750)
with pytest.raises(YunohostError):
install_legacy_app(secondary_domain, "/legacy")
# TODO check error message
assert app_is_not_installed(secondary_domain, "legacy_app")
def test_legacy_app_failed_remove(secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
# The remove script runs with set -eu and attempt to remove this
# file without -f, so will fail if it's not there ;)
os.remove("/etc/nginx/conf.d/%s.d/%s.conf" % (secondary_domain, "legacy_app"))
with pytest.raises(YunohostError):
app_remove("legacy")
#
# Well here, we hit the classical issue where if an app removal script
# fails, so far there's no obvious way to make sure that all files related
# to this app got removed ...
#
assert app_is_not_installed(secondary_domain, "legacy")
def test_systemfuckedup_during_app_install(secondary_domain):
with pytest.raises(YunohostError):
install_break_yo_system(secondary_domain, breakwhat="install")
os.system("nginx -t")
os.system("systemctl status nginx")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_remove(secondary_domain):
install_break_yo_system(secondary_domain, breakwhat="remove")
with pytest.raises(YunohostError):
app_remove("break_yo_system")
os.system("nginx -t")
os.system("systemctl status nginx")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_install_and_remove(secondary_domain):
with pytest.raises(YunohostError):
install_break_yo_system(secondary_domain, breakwhat="everything")
assert app_is_not_installed(secondary_domain, "break_yo_system")
def test_systemfuckedup_during_app_upgrade(secondary_domain):
install_break_yo_system(secondary_domain, breakwhat="upgrade")
with pytest.raises(YunohostError):
app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh")
def test_failed_multiple_app_upgrade(secondary_domain):
install_legacy_app(secondary_domain, "/legacy")
install_break_yo_system(secondary_domain, breakwhat="upgrade")
with pytest.raises(YunohostError):
app_upgrade(["break_yo_system", "legacy_app"],
file={"break_yo_system": "./tests/apps/break_yo_system_ynh",
"legacy": "./tests/apps/legacy_app_ynh"})