diff --git a/bin/yunohost b/bin/yunohost index 3e6f031b5..0f2385c10 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -5,10 +5,6 @@ import os import sys import argparse -import moulinette -from moulinette.actionsmap import ActionsMap -from moulinette.interfaces.cli import colorize, get_locale - # Either we are in a development environment or not IN_DEVEL = False @@ -35,6 +31,11 @@ if IN_DEVEL: LOG_DIR = os.path.join(basedir, 'log') +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize, get_locale + + # Initialization & helpers functions ----------------------------------- def _die(message, title='Error:'): @@ -50,7 +51,7 @@ def _parse_cli_args(): help="Don't use actions map cache", ) parser.add_argument('--output-as', - choices=['json', 'plain'], default=None, + choices=['json', 'plain', 'none'], default=None, help="Output result in another format", ) parser.add_argument('--debug', @@ -149,6 +150,11 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): 'handlers': [], 'propagate': True, }, + 'moulinette.interface': { + 'level': level, + 'handlers': handlers, + 'propagate': False, + }, }, 'root': { 'level': level, diff --git a/bin/yunohost-api b/bin/yunohost-api index f1370205e..d2b219f8b 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -5,10 +5,6 @@ import os import sys import argparse -import moulinette -from moulinette.actionsmap import ActionsMap -from moulinette.interfaces.cli import colorize - # Either we are in a development environment or not IN_DEVEL = False @@ -39,6 +35,11 @@ if IN_DEVEL: LOG_DIR = os.path.join(basedir, 'log') +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize + + # Initialization & helpers functions ----------------------------------- def _die(message, title='Error:'): diff --git a/bin/yunopaste b/bin/yunopaste new file mode 100755 index 000000000..d52199eba --- /dev/null +++ b/bin/yunopaste @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e +set -u + +PASTE_URL="https://paste.yunohost.org" + +_die() { + printf "Error: %s\n" "$*" + exit 1 +} + +check_dependencies() { + curl -V > /dev/null 2>&1 || _die "This script requires curl." +} + +paste_data() { + json=$(curl -X POST -s -d "$1" "${PASTE_URL}/documents") + [[ -z "$json" ]] && _die "Unable to post the data to the server." + + key=$(echo "$json" \ + | python -c 'import json,sys;o=json.load(sys.stdin);print o["key"]' \ + 2>/dev/null) + [[ -z "$key" ]] && _die "Unable to parse the server response." + + echo "${PASTE_URL}/${key}" +} + +usage() { + printf "Usage: ${0} [OPTION]... + +Read from input stream and paste the data to the YunoHost +Haste server. + +For example, to paste the output of the YunoHost diagnosis, you +can simply execute the following: + yunohost tools diagnosis | ${0} + +It will return the URL where you can access the pasted data. + +Options: + -h, --help show this help message and exit +" +} + +main() { + # parse options + while (( ${#} )); do + case "${1}" in + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown parameter detected: ${1}" >&2 + echo >&2 + usage >&2 + exit 1 + ;; + esac + + shift 1 + done + + # check input stream + read -t 0 || { + echo -e "Invalid usage: No input is provided.\n" >&2 + usage + exit 1 + } + + paste_data "$(cat)" +} + +check_dependencies + +main "${@}" diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e538bfc47..f51e93bd8 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -516,6 +516,7 @@ app: initdb: action_help: Create database and initialize it with optionnal attached script api: POST /tools/initdb + deprecated: true arguments: user: help: Name of the DB user @@ -956,60 +957,36 @@ service: default: 50 type: int - ### service_regenconf() - regenconf: - action_help: > - Regenerate the configuration file(s) for a service and compare the result - with the existing configuration file. - Prints the differences between files if any. + ### service_regen_conf() + regen-conf: + action_help: Regenerate the configuration file(s) for a service api: PUT /services/regenconf configuration: lock: false + deprecated_alias: + - regenconf arguments: - -s: - full: --service - help: Regenerate configuration for a specfic service - -f: - full: --force - help: Override the current configuration with the newly generated one, even if it has been modified + names: + help: Services name to regenerate configuration of + nargs: "*" + metavar: NAME + -d: + full: --with-diff + help: Show differences in case of configuration changes action: store_true - - ### service_safecopy() - safecopy: - action_help: > - Check if the specific file has been modified and display differences. - Stores the file hash in the services.yml file - arguments: - new_conf_file: - help: Path to the desired conf file - conf_file: - help: Path to the targeted conf file - -s: - full: --service - help: Service name attached to the conf file - extra: - required: True -f: full: --force - help: Override the current configuration with the newly generated one, even if it has been modified + help: > + Override all manual modifications in configuration + files action: store_true - - ### service_saferemove() - saferemove: - action_help: > - Check if the specific file has been modified before removing it. - Backup the file in /home/yunohost.backup - arguments: - conf_file: - help: Path to the targeted conf file - -s: - full: --service - help: Service name attached to the conf file - extra: - required: True - -f: - full: --force - help: Force file deletion + -n: + full: --dry-run + help: Show what would have been regenerated + action: store_true + -p: + full: --list-pending + help: List pending configuration files and exit action: store_true ############################# @@ -1378,8 +1355,15 @@ hook: nargs: "*" -a: full: --args - help: Ordered list of arguments to pass to the script + help: Ordered list of arguments to pass to the scripts nargs: "*" + -q: + full: --no-trace + help: Do not print each command that will be executed + action: store_true + -d: + full: --chdir + help: The directory from where the scripts will be executed ### hook_exec() exec: @@ -1389,7 +1373,8 @@ hook: help: Path of the script to execute -a: full: --args - help: Arguments to pass to the script + help: Ordered list of arguments to pass to the script + nargs: "*" --raise-on-error: help: Raise if the script returns a non-zero exit code action: store_true diff --git a/data/helpers.d/package b/data/helpers.d/package index dd78692f3..8955d5b25 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -48,27 +48,33 @@ ynh_package_install() { # usage: ynh_package_install_from_equivs controlfile # | arg: controlfile - path of the equivs control file ynh_package_install_from_equivs() { + controlfile=$1 + + # install equivs package as needed ynh_package_is_installed 'equivs' \ || ynh_package_install equivs # retrieve package information - pkgname=$(grep '^Package: ' $1 | cut -d' ' -f 2) - pkgversion=$(grep '^Version: ' $1 | cut -d' ' -f 2) + pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) + pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) [[ -z "$pkgname" || -z "$pkgversion" ]] \ && echo "Invalid control file" && exit 1 - controlfile=$(readlink -f "$1") # update packages cache ynh_package_update # build and install the package TMPDIR=$(ynh_mkdir_tmp) - (cd $TMPDIR \ - && equivs-build "$controlfile" 1>/dev/null \ + (cp "$controlfile" "${TMPDIR}/control" \ + && cd "$TMPDIR" \ + && equivs-build ./control 1>/dev/null \ && sudo dpkg --force-depends \ -i "./${pkgname}_${pkgversion}_all.deb" 2>&1 \ && sudo apt-get -f -y -qq install) \ && ([[ -n "$TMPDIR" ]] && rm -rf $TMPDIR) + + # check if the package is actually installed + ynh_package_is_installed "$pkgname" } # Remove package(s) diff --git a/data/helpers.d/user b/data/helpers.d/user index bfd044070..5ee6acd68 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -20,6 +20,17 @@ ynh_user_get_info() { sudo yunohost user info "$1" --output-as plain | ynh_get_plain_key "$2" } +# Get the list of YunoHost users +# +# example: for u in $(ynh_user_list); do ... +# +# usage: ynh_user_list +# | ret: string - one username per line +ynh_user_list() { + sudo yunohost user list --output-as plain --quiet \ + | awk '/^##username$/{getline; print}' +} + # Check if a user exists on the system # # usage: ynh_system_user_exists username diff --git a/data/hooks/backup/11-conf_ynh_mysql b/data/hooks/backup/11-conf_ynh_mysql index 5789901f1..435ba2807 100644 --- a/data/hooks/backup/11-conf_ynh_mysql +++ b/data/hooks/backup/11-conf_ynh_mysql @@ -1,4 +1,4 @@ backup_dir="$1/conf/ynh/mysql" sudo mkdir -p $backup_dir -sudo cp -a /etc/yunohost/mysql $backup_dir/ +sudo cp -a /etc/yunohost/mysql "${backup_dir}/root_pwd" diff --git a/data/hooks/backup/40-conf_ynh_currenthost b/data/hooks/backup/40-conf_ynh_currenthost index a91a50001..af054cad4 100644 --- a/data/hooks/backup/40-conf_ynh_currenthost +++ b/data/hooks/backup/40-conf_ynh_currenthost @@ -1,7 +1,4 @@ -backup_dir="$1/conf/ynh/" -backup_dir_legacy="$1/yunohost/" +backup_dir="$1/conf/ynh" sudo mkdir -p $backup_dir -sudo mkdir -p $backup_dir_legacy -sudo cp -a /etc/yunohost/current_host $backup_dir -sudo cp -a /etc/yunohost/current_host $backup_dir_legacy +sudo cp -a /etc/yunohost/current_host "${backup_dir}/current_host" diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost old mode 100644 new mode 100755 index d4a823f2e..96b62fe67 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -1,25 +1,111 @@ -set -e +#!/bin/bash -force=$1 +set -e -cd /usr/share/yunohost/templates/yunohost +services_path="/etc/yunohost/services.yml" -sudo mkdir -p /etc/yunohost +do_init_regen() { + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi -if [ ! -f /etc/yunohost/current_host ]; then - echo "yunohost.org" | sudo tee /etc/yunohost/current_host -fi + cd /usr/share/yunohost/templates/yunohost -if [ ! -f /etc/yunohost/firewall.yml ]; then - sudo cp firewall.yml /etc/yunohost/firewall.yml -fi + [[ -d /etc/yunohost ]] || mkdir -p /etc/yunohost -if [ ! -f /etc/yunohost/services.yml ]; then - sudo cp services.yml /etc/yunohost/services.yml -fi + # set default current_host + [[ -f /etc/yunohost/current_host ]] \ + || echo "yunohost.org" > /etc/yunohost/current_host -# Allow users to access /media directory -if [ ! -d /etc/skel/media ]; then - mkdir -p /media - ln -s /media /etc/skel/ -fi + # copy default services and firewall + [[ -f $services_path ]] \ + || cp services.yml "$services_path" + [[ -f /etc/yunohost/firewall.yml ]] \ + || cp firewall.yml /etc/yunohost/firewall.yml + + # allow users to access /media directory + [[ -d /etc/skel/media ]] \ + || (mkdir -p /media && ln -s /media /etc/skel/media) +} + +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/yunohost + + # update services.yml + if [[ -f $services_path ]]; then + tmp_services_path="${services_path}-tmp" + new_services_path="${services_path}-new" + sudo cp "$services_path" "$tmp_services_path" + _update_services "$new_services_path" || { + sudo mv "$tmp_services_path" "$services_path" + exit 1 + } + if [[ -f $new_services_path ]]; then + # replace services.yml with new one + sudo mv "$new_services_path" "$services_path" + sudo mv "$tmp_services_path" "${services_path}-old" + else + sudo rm -f "$tmp_services_path" + fi + else + sudo cp services.yml /etc/yunohost/services.yml + fi +} + +_update_services() { + sudo python2 - << EOF +import yaml +with open('services.yml') as f: + new_services = yaml.load(f) +with open('/etc/yunohost/services.yml') as f: + services = yaml.load(f) +updated = False +for service, conf in new_services.items(): + # remove service with empty conf + if not conf: + if service in services: + print("removing '{0}' from services".format(service)) + del services[service] + updated = True + # add new service + elif not services.get(service, None): + print("adding '{0}' to services".format(service)) + services[service] = conf + updated = True + # update service conf + else: + conffiles = services[service].pop('conffiles', {}) + if services[service] != conf: + print("update '{0}' service".format(service)) + services[service].update(conf) + updated = True + if conffiles: + services[service]['conffiles'] = conffiles +if updated: + with open('/etc/yunohost/services.yml-new', 'w') as f: + yaml.safe_dump(services, f, default_flow_style=False) +EOF +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + ;; + init) + do_init_regen + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/02-ssl b/data/hooks/conf_regen/02-ssl old mode 100644 new mode 100755 index baa7f13b7..60acdbbbb --- a/data/hooks/conf_regen/02-ssl +++ b/data/hooks/conf_regen/02-ssl @@ -1,64 +1,93 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [ ! -f /etc/yunohost/installed ]; then - sudo cp $1 $2 - else - if [ $force ]; then - sudo yunohost service safecopy \ - -s ssl $1 $2 --force - else - sudo yunohost service safecopy \ - -s ssl $1 $2 - fi - fi +ssl_dir="/usr/share/yunohost/yunohost-config/ssl/yunoCA" + +do_init_regen() { + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi + + # create certs and SSL directories + mkdir -p "/etc/yunohost/certs/yunohost.org" + mkdir -p "${ssl_dir}/"{ca,certs,crl,newcerts} + + # initialize some files + [[ -f "${ssl_dir}/serial" ]] \ + || echo "00" > "${ssl_dir}/serial" + [[ -f "${ssl_dir}/index.txt" ]] \ + || touch "${ssl_dir}/index.txt" + + openssl_conf="/usr/share/yunohost/templates/ssl/openssl.cnf" + + # create default certificates + if [[ ! -f /etc/yunohost/certs/yunohost.org/ca.pem ]]; then + openssl req -x509 -new -config "$openssl_conf" \ + -days 3650 -out "${ssl_dir}/ca/cacert.pem" \ + -keyout "${ssl_dir}/ca/cakey.pem" -nodes -batch 2>&1 + cp "${ssl_dir}/ca/cacert.pem" \ + /etc/yunohost/certs/yunohost.org/ca.pem + ln -sf /etc/yunohost/certs/yunohost.org/ca.pem \ + /etc/ssl/certs/ca-yunohost_crt.pem + update-ca-certificates + fi + + if [[ ! -f /etc/yunohost/certs/yunohost.org/crt.pem ]]; then + openssl req -new -config "$openssl_conf" \ + -days 730 -out "${ssl_dir}/certs/yunohost_csr.pem" \ + -keyout "${ssl_dir}/certs/yunohost_key.pem" -nodes -batch 2>&1 + openssl ca -config "$openssl_conf" \ + -days 730 -in "${ssl_dir}/certs/yunohost_csr.pem" \ + -out "${ssl_dir}/certs/yunohost_crt.pem" -batch 2>&1 + + last_cert=$(ls $ssl_dir/newcerts/*.pem | sort -V | tail -n 1) + chmod 640 "${ssl_dir}/certs/yunohost_key.pem" + chmod 640 "$last_cert" + + cp "${ssl_dir}/certs/yunohost_key.pem" \ + /etc/yunohost/certs/yunohost.org/key.pem + cp "$last_cert" \ + /etc/yunohost/certs/yunohost.org/crt.pem + ln -sf /etc/yunohost/certs/yunohost.org/crt.pem \ + /etc/ssl/certs/yunohost_crt.pem + ln -sf /etc/yunohost/certs/yunohost.org/key.pem \ + /etc/ssl/private/yunohost_key.pem + fi } -cd /usr/share/yunohost/templates/ssl -ssl_dir=/usr/share/yunohost/yunohost-config/ssl/yunoCA +do_pre_regen() { + pending_dir=$1 -sudo mkdir -p /etc/yunohost/certs/yunohost.org -sudo mkdir -p $ssl_dir/{ca,certs,crl,newcerts} + cd /usr/share/yunohost/templates/ssl -safe_copy openssl.cnf $ssl_dir/openssl.cnf + install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" +} -[ -f $ssl_dir/serial ] \ - || (echo "00" | sudo tee $ssl_dir/serial) +do_post_regen() { + regen_conf_files=$1 -[ -f $ssl_dir/index.txt ] \ - || sudo touch $ssl_dir/index.txt + # TODO: regenerate certificates if conf changed? +} -if [ ! -f /etc/yunohost/certs/yunohost.org/ca.pem ]; then - sudo openssl req -x509 -new -config $ssl_dir/openssl.cnf \ - -days 3650 -out $ssl_dir/ca/cacert.pem \ - -keyout $ssl_dir/ca/cakey.pem -nodes -batch - sudo cp $ssl_dir/ca/cacert.pem \ - /etc/yunohost/certs/yunohost.org/ca.pem - sudo ln -sf /etc/yunohost/certs/yunohost.org/ca.pem \ - /etc/ssl/certs/ca-yunohost_crt.pem - sudo update-ca-certificates -fi +FORCE=${2:-0} +DRY_RUN=${3:-0} -if [ ! -f /etc/yunohost/certs/yunohost.org/crt.pem ]; then - sudo openssl req -new -config $ssl_dir/openssl.cnf \ - -days 730 -out $ssl_dir/certs/yunohost_csr.pem \ - -keyout $ssl_dir/certs/yunohost_key.pem -nodes -batch - sudo openssl ca -config $ssl_dir/openssl.cnf \ - -days 730 -in $ssl_dir/certs/yunohost_csr.pem \ - -out $ssl_dir/certs/yunohost_crt.pem -batch +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + init) + do_init_regen + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac - last_cert=$(ls $ssl_dir/newcerts/*.pem | sort -V | tail -n 1) - sudo chmod 640 $ssl_dir/certs/yunohost_key.pem - sudo chmod 640 $last_cert - - sudo cp $ssl_dir/certs/yunohost_key.pem \ - /etc/yunohost/certs/yunohost.org/key.pem - sudo cp $last_cert \ - /etc/yunohost/certs/yunohost.org/crt.pem - sudo ln -sf /etc/yunohost/certs/yunohost.org/crt.pem \ - /etc/ssl/certs/yunohost_crt.pem - sudo ln -sf /etc/yunohost/certs/yunohost.org/key.pem \ - /etc/ssl/private/yunohost_key.pem -fi +exit 0 diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh old mode 100644 new mode 100755 index 1487ecdeb..a469b7a66 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -1,30 +1,45 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [ $force ]; then - sudo yunohost service safecopy \ - -s ssh \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s ssh \ - $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/ssh + + # only overwrite SSH configuration on an ISO installation + if [[ ! -f /etc/yunohost/from_script ]]; then + # do not listen to IPv6 if unavailable + [[ -f /proc/net/if_inet6 ]] \ + || sed -i "s/ListenAddress ::/#ListenAddress ::/g" sshd_config + + install -D -m 644 sshd_config "${pending_dir}/etc/ssh/sshd_config" + fi } -cd /usr/share/yunohost/templates/ssh +do_post_regen() { + regen_conf_files=$1 -# Only overwrite SSH configuration on an ISO installation -if [ ! -f /etc/yunohost/from_script ]; then + if [[ ! -f /etc/yunohost/from_script ]]; then + [[ -z "$regen_conf_files" ]] \ + || sudo service ssh restart + fi +} - # Do not listen to IPv6 if unavailable - if [ ! -f /proc/net/if_inet6 ]; then - sudo sed -i "s/ListenAddress ::/#ListenAddress ::/g" sshd_config - fi - safe_copy sshd_config /etc/ssh/sshd_config - - sudo service ssh restart -fi +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd old mode 100644 new mode 100755 index b5353394f..4316de5e4 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -1,71 +1,118 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [ ! -f /etc/yunohost/installed ]; then - sudo cp $1 $2 - else - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s slapd $1 $2 --force - else - sudo yunohost service safecopy \ - -s slapd $1 $2 - fi - fi +do_init_regen() { + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi + + do_pre_regen "" + + # fix some permissions + chown root:openldap /etc/ldap/slapd.conf + chown -R openldap:openldap /etc/ldap/schema/ + + # check the slapd config file at first + slaptest -Q -u -f /etc/ldap/slapd.conf + + # regenerate LDAP config directory from slapd.conf + rm -Rf /etc/ldap/slapd.d + mkdir /etc/ldap/slapd.d + slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 + chown -R openldap:openldap /etc/ldap/slapd.d/ + + service slapd restart } -cd /usr/share/yunohost/templates/slapd +do_pre_regen() { + pending_dir=$1 -# Remove legacy configuration file -[ ! -f /etc/yunohost/installed ] \ - || sudo yunohost service saferemove -s slapd \ - /etc/ldap/slapd-yuno.conf + cd /usr/share/yunohost/templates/slapd -# Retrieve current backend -backend=$(sudo slapcat -n 0 | sed -n 's/^dn: olcDatabase={1}\(.*\),cn=config$/\1/p') + # remove legacy configuration file + [ ! -f /etc/ldap/slapd-yuno.conf ] \ + || touch "${pending_dir}/etc/ldap/slapd-yuno.conf" -# Save current database in case of a backend change -BACKEND_CHANGE=0 -BACKUP_DIR="/var/backups/dc=yunohost,dc=org-${backend}-$(date +%s)" -if [[ -n "$backend" && "$backend" != "mdb" && "$force" == "True" ]]; then - BACKEND_CHANGE=1 - sudo mkdir -p "$BACKUP_DIR" - sudo slapcat -b dc=yunohost,dc=org \ - -l "${BACKUP_DIR}/dc=yunohost-dc=org.ldif" -fi + # create needed directories + ldap_dir="${pending_dir}/etc/ldap" + schema_dir="${ldap_dir}/schema" + mkdir -p "$ldap_dir" "$schema_dir" -safe_copy sudo.schema /etc/ldap/schema/sudo.schema -safe_copy mailserver.schema /etc/ldap/schema/mailserver.schema -safe_copy ldap.conf /etc/ldap/ldap.conf -safe_copy slapd.default /etc/default/slapd -safe_copy slapd.conf /etc/ldap/slapd.conf + # copy configuration files + cp -a ldap.conf slapd.conf "$ldap_dir" + cp -a sudo.schema mailserver.schema "$schema_dir" -# Fix some permissions -sudo chown root:openldap /etc/ldap/slapd.conf -sudo chown -R openldap:openldap /etc/ldap/schema/ -sudo chown -R openldap:openldap /etc/ldap/slapd.d/ + install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd" +} -# Check the slapd config file at first -sudo slaptest -Q -u -f /etc/ldap/slapd.conf +do_post_regen() { + regen_conf_files=$1 -if [[ $BACKEND_CHANGE -eq 1 ]]; then - # Regenerate LDAP config directory and import database as root - # since the admin user may be unavailable - sudo sh -c "rm -Rf /etc/ldap/slapd.d; -mkdir /etc/ldap/slapd.d; -slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d; -chown -R openldap:openldap /etc/ldap/slapd.d; -slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ - -l '${BACKUP_DIR}/dc=yunohost-dc=org.ldif'; -chown -R openldap:openldap /var/lib/ldap" 2>&1 -else - # Regenerate LDAP config directory from slapd.conf - sudo rm -Rf /etc/ldap/slapd.d - sudo mkdir /etc/ldap/slapd.d - sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 - sudo chown -R openldap:openldap /etc/ldap/slapd.d/ -fi + # fix some permissions + sudo chown root:openldap /etc/ldap/slapd.conf + sudo chown -R openldap:openldap /etc/ldap/schema/ + sudo chown -R openldap:openldap /etc/ldap/slapd.d/ -sudo service slapd force-reload + [ -z "$regen_conf_files" ] && exit 0 + + # retrieve current and new backends + curr_backend=$(sudo slapcat -n 0 \ + | sed -n 's/^dn: olcDatabase={1}\(.*\),cn=config$/\1/p') + new_backend=$(grep '^database' /etc/ldap/slapd.conf | awk '{print $2}') + + # save current database in case of a backend change + backend_change=0 + backup_dir="/var/backups/dc=yunohost,dc=org-${curr_backend}-$(date +%s)" + if [[ -n "$curr_backend" && "$curr_backend" != "$new_backend" ]]; then + backend_change=1 + sudo mkdir -p "$backup_dir" + sudo slapcat -b dc=yunohost,dc=org \ + -l "${backup_dir}/dc=yunohost-dc=org.ldif" + fi + + # check the slapd config file at first + sudo slaptest -Q -u -f /etc/ldap/slapd.conf + + if [[ $backend_change -eq 1 ]]; then + # regenerate LDAP config directory and import database as root + # since the admin user may be unavailable + sudo sh -c "rm -Rf /etc/ldap/slapd.d; + mkdir /etc/ldap/slapd.d; + slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d; + chown -R openldap:openldap /etc/ldap/slapd.d; + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ + -l '${backup_dir}/dc=yunohost-dc=org.ldif'; + chown -R openldap:openldap /var/lib/ldap" 2>&1 + else + # regenerate LDAP config directory from slapd.conf + sudo rm -Rf /etc/ldap/slapd.d + sudo mkdir /etc/ldap/slapd.d + sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 + sudo chown -R openldap:openldap /etc/ldap/slapd.d/ + fi + + sudo service slapd force-reload +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + init) + do_init_regen + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/09-nslcd b/data/hooks/conf_regen/09-nslcd old mode 100644 new mode 100755 index de377e997..5071ac1fd --- a/data/hooks/conf_regen/09-nslcd +++ b/data/hooks/conf_regen/09-nslcd @@ -1,26 +1,36 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s nslcd \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s nslcd \ - $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/nslcd + + install -D -m 644 nslcd.conf "${pending_dir}/etc/nslcd.conf" } -cd /usr/share/yunohost/templates/nslcd +do_post_regen() { + regen_conf_files=$1 -safe_copy nslcd.conf /etc/nslcd.conf + [[ -z "$regen_conf_files" ]] \ + || sudo service nslcd restart +} -# Fix: Add a blank line at the end of the file -# to avoid nscld restart failure -echo -e "\n" | sudo tee -a /etc/nslcd.conf +FORCE=${2:-0} +DRY_RUN=${3:-0} -sudo service nslcd restart +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome old mode 100644 new mode 100755 index abcf0d25d..4214722fc --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -1,66 +1,76 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s metronome \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s metronome \ - $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/metronome + + # create directories for pending conf + metronome_dir="${pending_dir}/etc/metronome" + metronome_conf_dir="${metronome_dir}/conf.d" + mkdir -p "$metronome_conf_dir" + + # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) + domain_list=$(sudo yunohost domain list --output-as plain --quiet) + + # install main conf file + cat metronome.cfg.lua \ + | sed "s/{{ main_domain }}/${main_domain}/g" \ + > "${metronome_dir}/metronome.cfg.lua" + + # add domain conf files + for domain in $domain_list; do + cat domain.tpl.cfg.lua \ + | sed "s/{{ domain }}/${domain}/g" \ + > "${metronome_conf_dir}/${domain}.cfg.lua" + done + + # remove old domain conf files + conf_files=$(ls -1 /etc/metronome/conf.d \ + | awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }') + for file in $conf_files; do + domain=${file%.cfg.lua} + [[ $domain_list =~ $domain ]] \ + || touch "${metronome_conf_dir}/${file}" + done } -cd /usr/share/yunohost/templates/metronome +do_post_regen() { + regen_conf_files=$1 -# Copy configuration files -main_domain=$(cat /etc/yunohost/current_host) -cat metronome.cfg.lua.sed \ - | sed "s/{{ main_domain }}/$main_domain/g" \ - | sudo tee metronome.cfg.lua -safe_copy metronome.cfg.lua /etc/metronome/metronome.cfg.lua + # fix some permissions + sudo chown -R metronome: /var/lib/metronome/ + sudo chown -R metronome: /etc/metronome/conf.d/ -need_restart=False -sudo mkdir -p /etc/metronome/conf.d + # retrieve variables + domain_list=$(sudo yunohost domain list --output-as plain --quiet) -domain_list=$(sudo yunohost domain list --output-as plain) + # create metronome directories for domains + for domain in $domain_list; do + sudo mkdir -p "/var/lib/metronome/${domain//./%2e}/pep" + done -# Copy a configuration file for each YunoHost domain -for domain in $domain_list; do - sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')" - sudo mkdir -p /var/lib/metronome/$sanitzed_domain/pep + [[ -z "$regen_conf_files" ]] \ + || sudo service metronome restart +} - cat domain.cfg.lua.sed \ - | sed "s/{{ domain }}/$domain/g" \ - | sudo tee $domain.cfg.lua - if [[ $(safe_copy $domain.cfg.lua /etc/metronome/conf.d/$domain.cfg.lua | tail -n1) == "True" ]]; then - need_restart=True - fi -done +FORCE=${2:-0} +DRY_RUN=${3:-0} -# Remove old domains files -for file in /etc/metronome/conf.d/*; do - domain=$(echo $file \ - | sed 's|/etc/metronome/conf.d/||' \ - | sed 's|.cfg.lua||') - sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')" - [[ $domain_list =~ $domain ]] \ - || ([[ $(sudo yunohost service saferemove -s metronome $file | tail -n1) == "True" ]] \ - && sudo rm -rf /var/lib/metronome/$sanitzed_domain) -done +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac -# Create domain directory -sudo chown -R metronome: /var/lib/metronome/ -sudo chown -R metronome: /etc/metronome/conf.d/ - -# Restart if need be -if [[ "$need_restart" == "True" ]]; then - sudo service metronome restart -else - sudo service metronome reload \ - || sudo service metronome restart -fi +exit 0 diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx old mode 100644 new mode 100755 index d45c0328e..9624878eb --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -1,86 +1,101 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [ ! -f /etc/yunohost/installed ]; then - sudo cp $1 $2 - else - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s nginx \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s nginx \ - $1 $2 - fi - fi +do_init_regen() { + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi + + do_pre_regen "" } -cd /usr/share/yunohost/templates/nginx +do_pre_regen() { + pending_dir=$1 -# Copy plain single configuration files -files="ssowat.conf -global.conf -yunohost_admin.conf -yunohost_admin.conf.inc -yunohost_api.conf.inc -yunohost_panel.conf.inc" + cd /usr/share/yunohost/templates/nginx -for file in $files; do - safe_copy $file /etc/nginx/conf.d/$file -done + nginx_dir="${pending_dir}/etc/nginx" + nginx_conf_dir="${nginx_dir}/conf.d" + mkdir -p "$nginx_conf_dir" + # install plain conf files + cp plain/* "$nginx_conf_dir" -if [ -f /etc/yunohost/installed ]; then + # probably run with init: just disable default site, restart NGINX and exit + if [[ -z "$pending_dir" ]]; then + rm -f "${nginx_dir}/sites-enabled/default" + service nginx restart + exit 0 + fi - need_restart=False - domain_list=$(sudo yunohost domain list --output-as plain) + # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) + domain_list=$(sudo yunohost domain list --output-as plain --quiet) - # Copy a configuration file for each YunoHost domain - for domain in $domain_list; do - sudo mkdir -p /etc/nginx/conf.d/$domain.d - cat server.conf.sed \ - | sed "s/{{ domain }}/$domain/g" \ - | sudo tee $domain.conf - [[ $(safe_copy $domain.conf /etc/nginx/conf.d/$domain.conf | tail -n1) == "True" ]] \ - && need_restart=True + # add domain conf files + for domain in $domain_list; do + domain_conf_dir="${nginx_conf_dir}/${domain}.d" + mkdir -p "$domain_conf_dir" - [ -f /etc/nginx/conf.d/$domain.d/yunohost_local.conf ] \ - && [[ $main_domain != $domain ]] \ - && sudo yunohost service saferemove -s nginx \ - /etc/nginx/conf.d/$domain.d/yunohost_local.conf - done + # NGINX server configuration + cat server.tpl.conf \ + | sed "s/{{ domain }}/${domain}/g" \ + > "${nginx_conf_dir}/${domain}.conf" + [[ $main_domain != $domain ]] \ + && touch "${domain_conf_dir}/yunohost_local.conf" \ + || cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf" + done - # Copy 'yunohost.local' to the main domain conf directory - main_domain=$(cat /etc/yunohost/current_host) - safe_copy yunohost_local.conf \ - /etc/nginx/conf.d/$main_domain.d/yunohost_local.conf + # remove old domain conf files + conf_files=$(ls -1 /etc/nginx/conf.d \ + | awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }') + for file in $conf_files; do + domain=${file%.conf} + [[ $domain_list =~ $domain ]] \ + || touch "${nginx_conf_dir}/${file}" + done + # disable default site + mkdir -p "${nginx_dir}/sites-enabled" + touch "${nginx_dir}/sites-enabled/default" +} - # Remove old domains files - for file in /etc/nginx/conf.d/*.*.conf; do - domain=$(echo $file \ - | sed 's|/etc/nginx/conf.d/||' \ - | sed 's|.conf||') - [[ $domain_list =~ $domain ]] \ - || ([[ $(sudo yunohost service saferemove -s nginx $file) == "True" ]] \ - && (sudo rm -r /etc/nginx/conf.d/$domain.d || true)) - done +do_post_regen() { + regen_conf_files=$1 -else - [ ! -f /etc/nginx/sites-available/default ] \ - || sudo rm -f /etc/nginx/sites-enabled/default - need_restart=True -fi + [ -z "$regen_conf_files" ] && exit 0 -# Restart if need be -if [[ "$need_restart" == "True" ]]; then - sudo service nginx restart -else - sudo service nginx reload \ - || sudo service nginx restart -fi + # retrieve variables + domain_list=$(sudo yunohost domain list --output-as plain --quiet) + + # create NGINX conf directories for domains + for domain in $domain_list; do + sudo mkdir -p "/etc/nginx/conf.d/${domain}.d" + done + + sudo service nginx restart +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + init) + do_init_regen + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix old mode 100644 new mode 100755 index 1e37e6752..3cb5cdf50 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -1,56 +1,56 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s postfix \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s postfix \ - $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/postfix + + postfix_dir="${pending_dir}/etc/postfix" + mkdir -p "$postfix_dir" + + # install plain conf files + cp plain/* "$postfix_dir" + + # prepare main.cf conf file + main_domain=$(cat /etc/yunohost/current_host) + cat main.cf \ + | sed "s/{{ main_domain }}/${main_domain}/g" \ + > "${postfix_dir}/main.cf" + + # adapt it for IPv4-only hosts + if [ ! -f /proc/net/if_inet6 ]; then + sed -i \ + 's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \ + "${postfix_dir}/main.cf" + sed -i \ + 's/inet_interfaces = all/&\ninet_protocols = ipv4/' \ + "${postfix_dir}/main.cf" + fi } -cd /usr/share/yunohost/templates/postfix +do_post_regen() { + regen_conf_files=$1 -# Copy plain single configuration files -files="header_checks -ldap-accounts.cf -ldap-aliases.cf -ldap-domains.cf -master.cf -sender_canonical -smtp_reply_filter" + [[ -z "$regen_conf_files" ]] \ + || sudo service postfix restart +} -for file in $files; do - safe_copy $file /etc/postfix/$file -done +FORCE=${2:-0} +DRY_RUN=${3:-0} -main_domain=$(cat /etc/yunohost/current_host) +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac -# Replace main domain in the main configuration file -cat main.cf.sed \ - | sed "s/{{ main_domain }}/$main_domain/g" \ - | sudo tee main.cf - -# And adapt it to IPv4-only hosts -if [ ! -f /proc/net/if_inet6 ]; then - sudo sed -i \ - 's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \ - main.cf - - sudo sed -i \ - 's/inet_interfaces = all/inet_interfaces = all\ninet_protocols = ipv4/' \ - main.cf -fi - -if [[ $(safe_copy main.cf /etc/postfix/main.cf) == "True" ]]; then - sudo service postfix restart -else - sudo service postfix reload \ - || sudo service postfix restart -fi +exit 0 diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy deleted file mode 100644 index 9c58f9349..000000000 --- a/data/hooks/conf_regen/22-email-legacy +++ /dev/null @@ -1,43 +0,0 @@ -set -e - -# Execute this hook only if we force the configuration regeneration -if [[ "$1" == "True" ]]; then - - # Add new email services - sudo yunohost service add rspamd -l /var/log/mail.log \ - || echo "rspamd is already listed in services" - - sudo yunohost service add rmilter -l /var/log/mail.log \ - || echo "rmilter is already listed in services" - - sudo yunohost service add redis-server -l /var/log/redis/redis-server.log \ - || echo "redis-server is already listed in services" - - # Remove previous email services - systemctl is-enabled spamassassin > /dev/null 2>&1 \ - && sudo systemctl disable spamassassin - systemctl is-active spamassassin > /dev/null \ - && sudo systemctl stop spamassassin - sudo rm -f /etc/cron.daily/spamassassin - sudo yunohost service status spamassassin > /dev/null 2>&1 \ - && sudo yunohost service remove spamassassin - - # 'systemctl is-enabled' does not work for service with no systemd unit file - sudo ls /etc/rc2.d/S??amavis > /dev/null 2>&1 \ - || sudo systemctl disable amavis - sudo systemctl is-active amavis > /dev/null \ - && sudo systemctl stop amavis - sudo yunohost service status amavis > /dev/null 2>&1 \ - && sudo yunohost service remove amavis - - # 'systemctl is-enabled' does not work for service with no systemd unit file - sudo ls /etc/rc2.d/S??postgrey > /dev/null 2>&1 \ - || sudo systemctl disable postgrey - sudo systemctl is-active postgrey > /dev/null \ - && sudo systemctl stop postgrey - sudo yunohost service status postgrey > /dev/null 2>&1 \ - && sudo yunohost service remove postgrey - -fi - -exit 0 diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot old mode 100644 new mode 100755 index c54300e08..37552c16b --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -1,51 +1,69 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s dovecot $1 $2 --force - else - sudo yunohost service safecopy \ - -s dovecot $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/dovecot + + dovecot_dir="${pending_dir}/etc/dovecot" + mkdir -p "${dovecot_dir}/global_script" + + # copy simple conf files + cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf" + cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve" + + # prepare dovecot.conf conf file + main_domain=$(cat /etc/yunohost/current_host) + cat dovecot.conf \ + | sed "s/{{ main_domain }}/${main_domain}/g" \ + > "${dovecot_dir}/dovecot.conf" + + # adapt it for IPv4-only hosts + if [ ! -f /proc/net/if_inet6 ]; then + sed -i \ + 's/^\(listen =\).*/\1 */' \ + "${dovecot_dir}/dovecot.conf" + fi } -cd /usr/share/yunohost/templates/dovecot +do_post_regen() { + regen_conf_files=$1 -# Create vmail user -sudo id vmail > /dev/null 2>&1 \ - || sudo adduser --system --ingroup mail --uid 500 vmail + # create vmail user + id vmail > /dev/null 2>&1 \ + || sudo adduser --system --ingroup mail --uid 500 vmail + # fix permissions + sudo chown -R vmail:mail /etc/dovecot/global_script + sudo chmod 770 /etc/dovecot/global_script -# Replace main domain in the main configuration file -main_domain=$(cat /etc/yunohost/current_host) -cat dovecot.conf.sed \ - | sed "s/{{ main_domain }}/$main_domain/g" \ - | sudo tee dovecot.conf + [ -z "$regen_conf_files" ] && exit 0 + # compile sieve script + [[ "$regen_conf_files" =~ dovecot\.sieve ]] && { + sudo sievec /etc/dovecot/global_script/dovecot.sieve + sudo chown -R vmail:mail /etc/dovecot/global_script + } -# Handle IPv4 only systems -if [ ! -f /proc/net/if_inet6 ]; -then - sudo sed -i 's/^listen.*/listen = \*/' dovecot.conf -fi + sudo service dovecot restart +} +FORCE=${2:-0} +DRY_RUN=${3:-0} -safe_copy dovecot.conf /etc/dovecot/dovecot.conf -safe_copy dovecot-ldap.conf /etc/dovecot/dovecot-ldap.conf +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac - -# Setup Sieve -sudo mkdir -p /etc/dovecot/global_script -sudo chmod -R 770 /etc/dovecot/global_script - -safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve -sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ - || safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve -sudo sievec /etc/dovecot/global_script/dovecot.sieve -sudo chmod 660 /etc/dovecot/global_script/dovecot.svbin -sudo chown -R vmail:mail /etc/dovecot/global_script - -sudo service dovecot restart +exit 0 diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter old mode 100644 new mode 100755 index f57427a2c..05f921e09 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -1,43 +1,69 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s rmilter $1 $2 --force - else - sudo yunohost service safecopy \ - -s rmilter $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/rmilter + + install -D -m 644 rmilter.conf \ + "${pending_dir}/etc/rmilter.conf" + install -D -m 644 rmilter.socket \ + "${pending_dir}/etc/systemd/system/rmilter.socket" } -cd /usr/share/yunohost/templates/rmilter +do_post_regen() { + regen_conf_files=$1 -# Copy Rmilter configuration -safe_copy rmilter.conf /etc/rmilter.conf + # retrieve variables + domain_list=$(sudo yunohost domain list --output-as plain --quiet) -# Override socket configuration -safe_copy rmilter.socket /etc/systemd/system/rmilter.socket + # create DKIM directory + sudo mkdir -p /etc/dkim -# Create DKIM key for each YunoHost domain -sudo mkdir -p /etc/dkim -domain_list=$(sudo yunohost domain list --output-as plain) + # create DKIM key for domains + for domain in $domain_list; do + domain_key="/etc/dkim/${domain}.mail.key" + [ ! -f $domain_key ] && { + sudo opendkim-genkey --domain="$domain" \ + --selector=mail --directory=/etc/dkim + sudo mv /etc/dkim/mail.private "$domain_key" + sudo mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" + } + done -for domain in $domain_list; do - [ -f /etc/dkim/$domain.mail.key ] \ - || (sudo opendkim-genkey --domain=$domain \ - --selector=mail\ - --directory=/etc/dkim \ - && sudo mv /etc/dkim/mail.private /etc/dkim/$domain.mail.key \ - && sudo mv /etc/dkim/mail.txt /etc/dkim/$domain.mail.txt) + # fix DKIM keys permissions + sudo chown _rmilter /etc/dkim/*.mail.key + sudo chmod 400 /etc/dkim/*.mail.key - sudo chown _rmilter /etc/dkim/$domain.mail.key - sudo chmod 400 /etc/dkim/$domain.mail.key -done + [ -z "$regen_conf_files" ] && exit 0 -# Reload systemd daemon, ensure that the socket is listening and stop -# the service. It will be started again by the socket as needed. -sudo systemctl daemon-reload -sudo systemctl start rmilter.socket -sudo systemctl stop rmilter.service 2>&1 || true + # reload systemd daemon + [[ "$regen_conf_files" =~ rmilter\.socket ]] && { + sudo systemctl -q daemon-reload + } + + # ensure that the socket is listening and stop the service - it will be + # started again by the socket as needed + sudo systemctl -q start rmilter.socket + sudo systemctl -q stop rmilter.service 2>&1 || true +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd old mode 100644 new mode 100755 index da62e20ea..327bedef1 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -1,33 +1,50 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s rspamd $1 $2 --force - else - sudo yunohost service safecopy \ - -s rspamd $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/rspamd + + install -D -m 644 metrics.local.conf \ + "${pending_dir}/etc/rspamd/local.d/metrics.conf" + install -D -m 644 rspamd.sieve \ + "${pending_dir}/etc/dovecot/global_script/rspamd.sieve" } -cd /usr/share/yunohost/templates/rspamd +do_post_regen() { + regen_conf_files=$1 -# Create configuration directories -sudo mkdir -p /etc/rspamd/local.d /etc/rspamd/override.d + [ -z "$regen_conf_files" ] && exit 0 -# Copy specific configuration to rewrite the defaults -safe_copy metrics.conf.local /etc/rspamd/local.d/metrics.conf + # compile sieve script + [[ "$regen_conf_files" =~ rspamd\.sieve ]] && { + sudo sievec /etc/dovecot/global_script/rspamd.sieve + sudo chown -R vmail:mail /etc/dovecot/global_script + sudo systemctl restart dovecot + } -# Install Rspamd sieve script -safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve -sudo sievec /etc/dovecot/global_script/rspamd.sieve -sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin -sudo chown -R vmail:mail /etc/dovecot/global_script + # ensure that the socket is listening and stop the service - it will be + # started again by the socket as needed + sudo systemctl -q start rspamd.socket + sudo systemctl -q stop rspamd.service 2>&1 || true +} -# Ensure that the socket is listening and stop the service. -sudo systemctl stop rspamd.service 2>&1 || true -sudo systemctl start rspamd.socket +FORCE=${2:-0} +DRY_RUN=${3:-0} -sudo systemctl restart dovecot +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql old mode 100644 new mode 100755 index 95fc97808..84f473d04 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -1,35 +1,82 @@ +#!/bin/bash + set -e -force=$1 +do_pre_regen() { + pending_dir=$1 -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s mysql $1 $2 --force - else - sudo yunohost service safecopy \ - -s mysql $1 $2 - fi + cd /usr/share/yunohost/templates/mysql + + install -D -m 644 my.cnf "${pending_dir}/etc/mysql/my.cnf" } -function randpass () { - [ "$2" == "0" ] && CHAR="[:alnum:]" || CHAR="[:graph:]" - cat /dev/urandom | tr -cd "$CHAR" | head -c ${1:-32} - echo +do_post_regen() { + regen_conf_files=$1 + + if [ ! -f /etc/yunohost/mysql ]; then + . /usr/share/yunohost/helpers.d/string + + # ensure that mysql is running + service mysql status >/dev/null 2>&1 \ + || service mysql start + + # generate and set new root password + mysql_password=$(ynh_string_random 10) + sudo mysqladmin -s -u root -pyunohost password "$mysql_password" || { + if [ $FORCE -eq 1 ]; then + . /usr/share/yunohost/helpers.d/package + + echo "It seems that you have already configured MySQL." \ + "YunoHost needs to have a root access to MySQL to runs its" \ + "applications, and is going to reset the MySQL root password." \ + "You can find this new password in /etc/yunohost/mysql." >&2 + + # retrieve MySQL package provider + ynh_package_is_installed "mariadb-server-10.0" \ + && mysql_pkg="mariadb-server-10.0" \ + || mysql_pkg="mysql-server-5.5" + + # set new password with debconf + sudo debconf-set-selections << EOF +$mysql_pkg mysql-server/root_password password $mysql_password +$mysql_pkg mysql-server/root_password_again password $mysql_password +EOF + + # reconfigure Debian package + sudo dpkg-reconfigure -freadline -u "$mysql_pkg" 2>&1 + else + echo "It seems that you have already configured MySQL." \ + "YunoHost needs to have a root access to MySQL to runs its" \ + "applications, but the MySQL root password is unknown." \ + "You must either pass --force to reset the password or" \ + "put the current one into the file /etc/yunohost/mysql." >&2 + exit 1 + fi + } + + # store new root password + echo "$mysql_password" | sudo tee /etc/yunohost/mysql + sudo chmod 400 /etc/yunohost/mysql + fi + + [[ -z "$regen_conf_files" ]] \ + || sudo service mysql restart } -cd /usr/share/yunohost/templates/mysql +FORCE=${2:-0} +DRY_RUN=${3:-0} -if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf | tail -n1)" == "True" ]]; then - sudo service mysql restart -fi +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac -if [ ! -f /etc/yunohost/mysql ]; then - [[ $(/bin/ps aux | grep '[m]ysqld') == "0" ]] \ - && sudo service mysql start - - mysql_password=$(randpass 10 0) - sudo mysqladmin -u root -pyunohost password $mysql_password - echo $mysql_password | sudo tee /etc/yunohost/mysql - sudo chmod 400 /etc/yunohost/mysql -fi +exit 0 diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon old mode 100644 new mode 100755 index 0fed5217c..655a2e054 --- a/data/hooks/conf_regen/37-avahi-daemon +++ b/data/hooks/conf_regen/37-avahi-daemon @@ -1,19 +1,37 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s avahi-daemon $1 $2 --force - else - sudo yunohost service safecopy \ - -s avahi-daemon $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/avahi-daemon + + install -D -m 644 avahi-daemon.conf \ + "${pending_dir}/etc/avahi/avahi-daemon.conf" } -cd /usr/share/yunohost/templates/avahi-daemon +do_post_regen() { + regen_conf_files=$1 -if [[ "$(safe_copy avahi-daemon.conf /etc/avahi/avahi-daemon.conf | tail -n1)" == "True" ]]; then - sudo service avahi-daemon restart -fi + [[ -z "$regen_conf_files" ]] \ + || sudo service avahi-daemon restart +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/40-glances b/data/hooks/conf_regen/40-glances old mode 100644 new mode 100755 index a1bec3dbe..a19d35d56 --- a/data/hooks/conf_regen/40-glances +++ b/data/hooks/conf_regen/40-glances @@ -1,19 +1,36 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s glances $1 $2 --force - else - sudo yunohost service safecopy \ - -s glances $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/glances + + install -D -m 644 glances.default "${pending_dir}/etc/default/glances" } -cd /usr/share/yunohost/templates/glances +do_post_regen() { + regen_conf_files=$1 -if [[ "$(safe_copy glances.default /etc/default/glances | tail -n1)" == "True" ]]; then - sudo service glances restart -fi + [[ -z "$regen_conf_files" ]] \ + || sudo service glances restart +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq old mode 100644 new mode 100755 index 683747adb..6947fb634 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -1,53 +1,66 @@ -set -e +#!/bin/bash -force=$1 +set -e -. /usr/share/yunohost/helpers +do_pre_regen() { + pending_dir=$1 -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s dnsmasq $1 $2 --force - else - sudo yunohost service safecopy \ - -s dnsmasq $1 $2 - fi + # source ip helpers + . /usr/share/yunohost/helpers.d/ip + + cd /usr/share/yunohost/templates/dnsmasq + + # create directory for pending conf + dnsmasq_dir="${pending_dir}/etc/dnsmasq.d" + mkdir -p "$dnsmasq_dir" + + # retrieve variables + ipv4=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true) + ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' + ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null || true) + ynh_validate_ip6 "$ipv6" || ipv6='' + domain_list=$(sudo yunohost domain list --output-as plain --quiet) + + # add domain conf files + for domain in $domain_list; do + cat domain.tpl \ + | sed "s/{{ domain }}/${domain}/g" \ + | sed "s/{{ ip }}/${ipv4}/g" \ + > "${dnsmasq_dir}/${domain}" + [[ -n $ipv6 ]] \ + && echo "address=/${domain}/${ipv6}" >> "${dnsmasq_dir}/${domain}" + done + + # remove old domain conf files + conf_files=$(ls -1 /etc/dnsmasq.d \ + | awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }') + for domain in $conf_files; do + [[ $domain_list =~ $domain ]] \ + || touch "${dnsmasq_dir}/${domain}" + done } -cd /usr/share/yunohost/templates/dnsmasq +do_post_regen() { + regen_conf_files=$1 -# Get IPv4 address -ip=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true) -ynh_validate_ip4 $ip || ip='0.0.0.0' + [[ -z "$regen_conf_files" ]] \ + || sudo service dnsmasq restart +} -# Get IPv6 IP address -ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null || true) -ynh_validate_ip6 $ipv6 || ipv6='' +FORCE=${2:-0} +DRY_RUN=${3:-0} -sudo mkdir -p /etc/dnsmasq.d +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac -domain_list=$(sudo yunohost domain list --output-as plain) - -# Copy a configuration file for each YunoHost domain -for domain in $domain_list; do - cat domain.sed \ - | sed "s/{{ domain }}/$domain/g" \ - | sed "s/{{ ip }}/$ip/g" \ - | sudo tee $domain - - if [[ "$ipv6" != "" ]]; then - echo "address=/$domain/$ipv6" | sudo tee -a $domain - fi - - safe_copy $domain /etc/dnsmasq.d/$domain -done - -# Remove old domains files -for file in /etc/dnsmasq.d/*.*; do - domain=$(echo $file | sed 's|/etc/dnsmasq.d/||') - [[ $domain_list =~ $domain ]] \ - || sudo yunohost service saferemove -s dnsmasq $file -done - -sudo service dnsmasq reload \ - || sudo service dnsmasq restart +exit 0 diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch old mode 100644 new mode 100755 index 92f34b249..db3a2199a --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -1,19 +1,36 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s nsswitch $1 $2 --force - else - sudo yunohost service safecopy \ - -s nsswitch $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/nsswitch + + install -D -m 644 nsswitch.conf "${pending_dir}/etc/nsswitch.conf" } -cd /usr/share/yunohost/templates/nsswitch +do_post_regen() { + regen_conf_files=$1 -if [[ "$(safe_copy nsswitch.conf /etc/nsswitch.conf | tail -n1)" == "True" ]]; then - sudo service nscd restart -fi + [[ -z "$regen_conf_files" ]] \ + || sudo service nscd restart +} + +FORCE=${2:-0} +DRY_RUN=${3:-0} + +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban old mode 100644 new mode 100755 index 9ff6714ad..1c262078b --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -1,28 +1,40 @@ -set -e +#!/bin/bash -force=$1 +set -e -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s fail2ban $1 $2 --force - else - sudo yunohost service safecopy \ - -s fail2ban $1 $2 - fi +do_pre_regen() { + pending_dir=$1 + + cd /usr/share/yunohost/templates/fail2ban + + fail2ban_dir="${pending_dir}/etc/fail2ban" + mkdir -p "${fail2ban_dir}/filter.d" + + cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf" + cp jail.conf "${fail2ban_dir}/jail.conf" } -cd /usr/share/yunohost/templates/fail2ban +do_post_regen() { + regen_conf_files=$1 -sudo mkdir -p /etc/fail2ban/filter.d -safe_copy yunohost.conf /etc/fail2ban/filter.d/yunohost.conf + [[ -z "$regen_conf_files" ]] \ + || sudo service fail2ban restart +} -# Compatibility: change from HDB to MDB on Jessie -version=$(sed 's/\..*//' /etc/debian_version) -[[ "$version" == '8' ]] \ - && sudo cp jail-jessie.conf jail.conf \ - || sudo cp jail-wheezy.conf jail.conf +FORCE=${2:-0} +DRY_RUN=${3:-0} -if [[ $(safe_copy jail.conf /etc/fail2ban/jail.conf | tail -n1) == "True" ]]; then - sudo service fail2ban restart -fi +case "$1" in + pre) + do_pre_regen $4 + ;; + post) + do_post_regen $4 + ;; + *) + echo "hook called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/data/hooks/restore/11-conf_ynh_mysql b/data/hooks/restore/11-conf_ynh_mysql index e4d71b369..b2f8c8e31 100644 --- a/data/hooks/restore/11-conf_ynh_mysql +++ b/data/hooks/restore/11-conf_ynh_mysql @@ -1,6 +1,42 @@ backup_dir="$1/conf/ynh/mysql" -sudo service mysql restart -sudo cp -a $backup_dir/mysql /etc/yunohost/mysql -mysqlpwd=$(sudo cat /etc/yunohost/mysql) -sudo mysqladmin flush-privileges -p"$mysqlpwd" +# ensure that mysql is running +service mysql status >/dev/null 2>&1 \ + || service mysql start + +# retrieve current and new password +[ -f /etc/yunohost/mysql ] \ + && curr_pwd=$(sudo cat /etc/yunohost/mysql) \ + || curr_pwd="yunohost" +new_pwd=$(sudo cat "${backup_dir}/root_pwd" || sudo cat "${backup_dir}/mysql") + +# attempt to change it +sudo mysqladmin -s -u root -p"$curr_pwd" password "$new_pwd" || { + . /usr/share/yunohost/helpers.d/package + + echo "It seems that you have already configured MySQL." \ + "YunoHost needs to have a root access to MySQL to runs its" \ + "applications, and is going to reset the MySQL root password." \ + "You can find this new password in /etc/yunohost/mysql." >&2 + + # retrieve MySQL package provider + ynh_package_is_installed "mariadb-server-10.0" \ + && mysql_pkg="mariadb-server-10.0" \ + || mysql_pkg="mysql-server-5.5" + + # set new password with debconf + sudo debconf-set-selections << EOF +$mysql_pkg mysql-server/root_password password $new_pwd +$mysql_pkg mysql-server/root_password_again password $new_pwd +EOF + + # reconfigure Debian package + sudo dpkg-reconfigure -freadline -u "$mysql_pkg" 2>&1 +} + +# store new root password +echo "$new_pwd" | sudo tee /etc/yunohost/mysql +sudo chmod 400 /etc/yunohost/mysql + +# reload the grant tables +sudo mysqladmin -s -u root -p"$new_pwd" reload diff --git a/data/hooks/restore/40-conf_ynh_currenthost b/data/hooks/restore/40-conf_ynh_currenthost index 8b1378917..a0bdf94d3 100644 --- a/data/hooks/restore/40-conf_ynh_currenthost +++ b/data/hooks/restore/40-conf_ynh_currenthost @@ -1 +1,3 @@ +backup_dir="$1/conf/ynh" +sudo cp -a "${backup_dir}/current_host" /etc/yunohost/current_host diff --git a/data/templates/dnsmasq/domain.sed b/data/templates/dnsmasq/domain.tpl similarity index 100% rename from data/templates/dnsmasq/domain.sed rename to data/templates/dnsmasq/domain.tpl diff --git a/data/templates/dovecot/dovecot.conf.sed b/data/templates/dovecot/dovecot.conf similarity index 100% rename from data/templates/dovecot/dovecot.conf.sed rename to data/templates/dovecot/dovecot.conf diff --git a/data/templates/fail2ban/jail-wheezy.conf b/data/templates/fail2ban/jail-wheezy.conf deleted file mode 100644 index 8eb0e7a1e..000000000 --- a/data/templates/fail2ban/jail-wheezy.conf +++ /dev/null @@ -1,346 +0,0 @@ -# Fail2Ban configuration file. -# -# This file was composed for Debian systems from the original one -# provided now under /usr/share/doc/fail2ban/examples/jail.conf -# for additional examples. -# -# To avoid merges during upgrades DO NOT MODIFY THIS FILE -# and rather provide your changes in /etc/fail2ban/jail.local -# -# Author: Yaroslav O. Halchenko -# -# $Revision$ -# - -# The DEFAULT allows a global definition of the options. They can be overridden -# in each jail afterwards. - -[DEFAULT] - -# "ignoreip" can be an IP address, a CIDR mask or a DNS host -ignoreip = 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 -bantime = 600 -maxretry = 3 - -# "backend" specifies the backend used to get files modification. Available -# options are "gamin", "polling" and "auto". -# yoh: For some reason Debian shipped python-gamin didn't work as expected -# This issue left ToDo, so polling is default backend for now -backend = auto - -# -# Destination email address used solely for the interpolations in -# jail.{conf,local} configuration files. -destemail = root@localhost - -# -# ACTIONS -# - -# Default banning action (e.g. iptables, iptables-new, -# iptables-multiport, shorewall, etc) It is used to define -# action_* variables. Can be overridden globally or per -# section within jail.local file -banaction = iptables-multiport - -# email action. Since 0.8.1 upstream fail2ban uses sendmail -# MTA for the mailing. Change mta configuration parameter to mail -# if you want to revert to conventional 'mail'. -mta = sendmail - -# Default protocol -protocol = tcp - -# Specify chain where jumps would need to be added in iptables-* actions -chain = INPUT - -# -# Action shortcuts. To be used to define action parameter - -# The simplest action to take: ban only -action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - -# ban & send an e-mail with whois report to the destemail. -action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] - -# ban & send an e-mail with whois report and relevant log lines -# to the destemail. -action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"] - -# Choose default action. To change, just override value of 'action' with the -# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local -# globally (section [DEFAULT]) or per specific section -action = %(action_)s - -# -# JAILS -# - -# Next jails corresponds to the standard configuration in Fail2ban 0.6 which -# was shipped in Debian. Enable any defined here jail by including -# -# [SECTION_NAME] -# enabled = true - -# -# in /etc/fail2ban/jail.local. -# -# Optionally you may override any other parameter (e.g. banaction, -# action, port, logpath, etc) in that section within jail.local - -[ssh] - -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 6 - -[dropbear] - -enabled = false -port = ssh -filter = sshd -logpath = /var/log/dropbear -maxretry = 6 - -# Generic filter for pam. Has to be used with action which bans all ports -# such as iptables-allports, shorewall -[pam-generic] - -enabled = false -# pam-generic filter can be customized to monitor specific subset of 'tty's -filter = pam-generic -# port actually must be irrelevant but lets leave it all for some possible uses -port = all -banaction = iptables-allports -port = anyport -logpath = /var/log/auth.log -maxretry = 6 - -[xinetd-fail] - -enabled = false -filter = xinetd-fail -port = all -banaction = iptables-multiport-log -logpath = /var/log/daemon.log -maxretry = 2 - - -[ssh-ddos] - -enabled = false -port = ssh -filter = sshd-ddos -logpath = /var/log/auth.log -maxretry = 6 - -# -# HTTP servers -# - -[apache] - -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 - -# default action is now multiport, so apache-multiport jail was left -# for compatibility with previous (<0.7.6-2) releases -[apache-multiport] - -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 - -[apache-noscript] - -enabled = false -port = http,https -filter = apache-noscript -logpath = /var/log/apache*/*error.log -maxretry = 6 - -[apache-overflows] - -enabled = false -port = http,https -filter = apache-overflows -logpath = /var/log/apache*/*error.log -maxretry = 2 - -# -# FTP servers -# - -[vsftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = vsftpd -logpath = /var/log/vsftpd.log -# or overwrite it in jails.local to be -# logpath = /var/log/auth.log -# if you want to rely on PAM failed login attempts -# vsftpd's failregex should match both of those formats -maxretry = 6 - - -[proftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = proftpd -logpath = /var/log/proftpd/proftpd.log -maxretry = 6 - - -[pure-ftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = pure-ftpd -logpath = /var/log/auth.log -maxretry = 6 - - -[wuftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = wuftpd -logpath = /var/log/auth.log -maxretry = 6 - - -# -# Mail servers -# - -[postfix] - -enabled = true -port = smtp,ssmtp -filter = postfix -logpath = /var/log/mail.log - -[couriersmtp] - -enabled = false -port = smtp,ssmtp -filter = couriersmtp -logpath = /var/log/mail.log - - -# -# Mail servers authenticators: might be used for smtp,ftp,imap servers, so -# all relevant ports get banned -# - -[courierauth] - -enabled = false -port = smtp,ssmtp,imap2,imap3,imaps,pop3,pop3s -filter = courierlogin -logpath = /var/log/mail.log - - -[sasl] - -enabled = true -port = smtp,ssmtp,imap2,imap3,imaps,pop3,pop3s -filter = sasl -# You might consider monitoring /var/log/mail.warn instead if you are -# running postfix since it would provide the same log lines at the -# "warn" level but overall at the smaller filesize. -logpath = /var/log/mail.log - -[dovecot] - -enabled = true -port = smtp,ssmtp,imap2,imap3,imaps,pop3,pop3s -filter = dovecot -logpath = /var/log/mail.log - - -# DNS Servers - - -# These jails block attacks against named (bind9). By default, logging is off -# with bind9 installation. You will need something like this: -# -# logging { -# channel security_file { -# file "/var/log/named/security.log" versions 3 size 30m; -# severity dynamic; -# print-time yes; -# }; -# category security { -# security_file; -# }; -# }; -# -# in your named.conf to provide proper logging - -# !!! WARNING !!! -# Since UDP is connection-less protocol, spoofing of IP and imitation -# of illegal actions is way too simple. Thus enabling of this filter -# might provide an easy way for implementing a DoS against a chosen -# victim. See -# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html -# Please DO NOT USE this jail unless you know what you are doing. -#[named-refused-udp] -# -#enabled = false -#port = domain,953 -#protocol = udp -#filter = named-refused -#logpath = /var/log/named/security.log - -[named-refused-tcp] - -enabled = false -port = domain,953 -protocol = tcp -filter = named-refused -logpath = /var/log/named/security.log - -[nginx] - -enabled = true -port = http,https -filter = apache-auth -logpath = /var/log/nginx*/*error.log -maxretry = 6 - -[nginx-noscript] - -enabled = false -port = http,https -filter = apache-noscript -logpath = /var/log/nginx*/*error.log -maxretry = 6 - -[nginx-overflows] - -enabled = false -port = http,https -filter = apache-overflows -logpath = /var/log/nginx*/*error.log -maxretry = 4 - -[yunohost] - -enabled = true -port = http,https -protocol = tcp -filter = yunohost -logpath = /var/log/nginx/*.log -maxretry = 6 diff --git a/data/templates/fail2ban/jail-jessie.conf b/data/templates/fail2ban/jail.conf similarity index 100% rename from data/templates/fail2ban/jail-jessie.conf rename to data/templates/fail2ban/jail.conf diff --git a/data/templates/metronome/domain.cfg.lua.sed b/data/templates/metronome/domain.tpl.cfg.lua similarity index 100% rename from data/templates/metronome/domain.cfg.lua.sed rename to data/templates/metronome/domain.tpl.cfg.lua diff --git a/data/templates/metronome/metronome.cfg.lua.sed b/data/templates/metronome/metronome.cfg.lua similarity index 100% rename from data/templates/metronome/metronome.cfg.lua.sed rename to data/templates/metronome/metronome.cfg.lua diff --git a/data/templates/nginx/global.conf b/data/templates/nginx/plain/global.conf similarity index 100% rename from data/templates/nginx/global.conf rename to data/templates/nginx/plain/global.conf diff --git a/data/templates/nginx/ssowat.conf b/data/templates/nginx/plain/ssowat.conf similarity index 100% rename from data/templates/nginx/ssowat.conf rename to data/templates/nginx/plain/ssowat.conf diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf similarity index 100% rename from data/templates/nginx/yunohost_admin.conf rename to data/templates/nginx/plain/yunohost_admin.conf diff --git a/data/templates/nginx/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc similarity index 100% rename from data/templates/nginx/yunohost_admin.conf.inc rename to data/templates/nginx/plain/yunohost_admin.conf.inc diff --git a/data/templates/nginx/yunohost_api.conf.inc b/data/templates/nginx/plain/yunohost_api.conf.inc similarity index 100% rename from data/templates/nginx/yunohost_api.conf.inc rename to data/templates/nginx/plain/yunohost_api.conf.inc diff --git a/data/templates/nginx/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc similarity index 100% rename from data/templates/nginx/yunohost_panel.conf.inc rename to data/templates/nginx/plain/yunohost_panel.conf.inc diff --git a/data/templates/nginx/server.conf.sed b/data/templates/nginx/server.tpl.conf similarity index 100% rename from data/templates/nginx/server.conf.sed rename to data/templates/nginx/server.tpl.conf diff --git a/data/templates/postfix/main.cf.sed b/data/templates/postfix/main.cf similarity index 100% rename from data/templates/postfix/main.cf.sed rename to data/templates/postfix/main.cf diff --git a/data/templates/postfix/header_checks b/data/templates/postfix/plain/header_checks similarity index 100% rename from data/templates/postfix/header_checks rename to data/templates/postfix/plain/header_checks diff --git a/data/templates/postfix/ldap-accounts.cf b/data/templates/postfix/plain/ldap-accounts.cf similarity index 100% rename from data/templates/postfix/ldap-accounts.cf rename to data/templates/postfix/plain/ldap-accounts.cf diff --git a/data/templates/postfix/ldap-aliases.cf b/data/templates/postfix/plain/ldap-aliases.cf similarity index 100% rename from data/templates/postfix/ldap-aliases.cf rename to data/templates/postfix/plain/ldap-aliases.cf diff --git a/data/templates/postfix/ldap-domains.cf b/data/templates/postfix/plain/ldap-domains.cf similarity index 100% rename from data/templates/postfix/ldap-domains.cf rename to data/templates/postfix/plain/ldap-domains.cf diff --git a/data/templates/postfix/master.cf b/data/templates/postfix/plain/master.cf similarity index 100% rename from data/templates/postfix/master.cf rename to data/templates/postfix/plain/master.cf diff --git a/data/templates/postfix/sender_canonical b/data/templates/postfix/plain/sender_canonical similarity index 100% rename from data/templates/postfix/sender_canonical rename to data/templates/postfix/plain/sender_canonical diff --git a/data/templates/postfix/smtp_reply_filter b/data/templates/postfix/plain/smtp_reply_filter similarity index 100% rename from data/templates/postfix/smtp_reply_filter rename to data/templates/postfix/plain/smtp_reply_filter diff --git a/data/templates/rspamd/metrics.conf.local b/data/templates/rspamd/metrics.local.conf similarity index 100% rename from data/templates/rspamd/metrics.conf.local rename to data/templates/rspamd/metrics.local.conf diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index f8dc324d3..b4e63479b 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -15,8 +15,13 @@ postfix: log: [/var/log/mail.log,/var/log/mail.err] rmilter: status: systemctl status rmilter.socket + log: /var/log/mail.log rspamd: status: systemctl status rspamd.socket + log: /var/log/mail.log +redis-server: + status: service + log: /var/log/redis/redis-server.log mysql: status: service log: [/var/log/mysql.log,/var/log/mysql.err] @@ -39,9 +44,6 @@ yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: status: service -postgrey: - status: service - log: /var/log/mail.log nslcd: status: service log: /var/log/syslog @@ -49,3 +51,6 @@ nsswitch: status: service udisks2: status: service +amavis: null +postgrey: null +spamassassin: null diff --git a/debian/changelog b/debian/changelog index 81124e12b..4b5f37391 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,42 @@ +yunohost (2.3.12.1) testing; urgency=low + + * [deb] Rely on dh_installinit to restart yunohost-firewall after upgrade + * [deb] Add Install section to yunohost-firewall.service + + -- Jérôme Lebleu Sat, 09 Apr 2016 17:22:40 +0200 + +yunohost (2.3.12) testing; urgency=low + + [ Jérôme Lebleu ] + * [enh] Use new rspamd configuration system to override metrics + * [enh] Allow to set script execution directory in hook_exec + * [enh] Add a ynh_user_list helper + * [enh] Call app remove script if installation fails + * [fix] Move imports at the top in yunohost and yunohost-api + * [fix] Use rspamd local.d folder to allow users to override the defaults + * [fix] Execute backup/restore app scripts from the backup dir (bugfix #139) + * [fix] Regenerate SSOwat conf after apps restoration + * [fix] Move imports at the top in backup.py + * [fix] Check if the package is actually installed in equivs helper + * [fix] Improve control file management in equivs helper + * [fix] Remove ending comma in backup.py + * [fix] Call yunohost commands with --quiet in setting helpers + * [fix] Check for tty in root_handlers before remove it in bin/yunohost + * [fix] Use dyndns.yunohost.org instead of dynhost.yunohost.org + * [fix] Set found private key and don't validate it in dyndns_update + * [fix] Update first registered domain with DynDNS instead of current_host + * [i18n] Rename app_requirements_failed err named variable + * [i18n] Update translations from Weblate + + [ opi ] + * [enh] Better message during service regenconf. + * [enh] Display hook path on error message. + * [enh] Use named arguments when calling m18n in service.py + * [enh] Use named arguments with m18n. + * [enh] Use named arguments for user_unknown string. + + -- Jérôme Lebleu Sat, 09 Apr 2016 12:13:10 +0200 + moulinette-yunohost (2.2.4) stable; urgency=low [ Jérôme Lebleu ] diff --git a/debian/control b/debian/control index 2053e9d60..80f562e76 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends} - , moulinette (>= 2.3.4) + , moulinette (>= 2.3.5.1) , python-psutil, python-requests, python-dnspython , python-apt, python-miniupnpc , glances @@ -27,7 +27,7 @@ Depends: ${python:Depends}, ${misc:Depends} , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools Recommends: yunohost-admin , openssh-server, ntp, inetutils-ping | iputils-ping - , bash-completion, rsyslog + , bash-completion, rsyslog, etckeeper , php5-gd, php5-curl, php-gettext, php5-mcrypt , python-pip , unattended-upgrades diff --git a/debian/postinst b/debian/postinst index 513d9bf35..c67d432ab 100644 --- a/debian/postinst +++ b/debian/postinst @@ -6,13 +6,13 @@ do_configure() { rm -rf /var/cache/moulinette/* if [ ! -f /etc/yunohost/installed ]; then - bash /usr/share/yunohost/hooks/conf_regen/01-yunohost True - bash /usr/share/yunohost/hooks/conf_regen/02-ssl True - bash /usr/share/yunohost/hooks/conf_regen/06-slapd True - bash /usr/share/yunohost/hooks/conf_regen/15-nginx True + bash /usr/share/yunohost/hooks/conf_regen/01-yunohost init + bash /usr/share/yunohost/hooks/conf_regen/02-ssl init + bash /usr/share/yunohost/hooks/conf_regen/06-slapd init + bash /usr/share/yunohost/hooks/conf_regen/15-nginx init else echo "Regenerating configuration, this might take a while..." - yunohost service regenconf + yunohost service regen-conf --output-as none # restart yunohost-firewall if it's running service yunohost-firewall status >/dev/null \ @@ -28,23 +28,18 @@ do_configure() { restart_yunohost_firewall() { echo "Restarting YunoHost firewall..." - if [ -x /etc/init.d/yunohost-firewall ]; then - update-rc.d yunohost-firewall defaults >/dev/null || true - if [ -d /run/systemd/system ]; then - systemctl --system daemon-reload >/dev/null || true - else - invoke-rc.d yunohost-firewall start >/dev/null || true - fi - fi - deb-systemd-helper unmask yunohost-firewall.service >/dev/null || true if deb-systemd-helper --quiet was-enabled yunohost-firewall.service; then deb-systemd-helper enable yunohost-firewall.service >/dev/null || true + else + deb-systemd-helper update-state yunohost-firewall.service >/dev/null || true fi - deb-systemd-helper update-state yunohost-firewall.service >/dev/null || true - if [ -d /run/systemd/system ]; then - systemctl --system daemon-reload >/dev/null || true - deb-systemd-invoke try-restart yunohost-firewall.service >/dev/null || true + + if [ -x /etc/init.d/yunohost-firewall ]; then + update-rc.d yunohost-firewall enable >/dev/null + if [ -n "$2" ]; then + invoke-rc.d yunohost-firewall restart >/dev/null || exit $? + fi fi } @@ -71,16 +66,6 @@ case "$1" in ;; esac -# Enable and start yunohost-api sysv service -if [ -x /etc/init.d/yunohost-api ]; then - update-rc.d yunohost-api defaults >/dev/null - if [ -d /run/systemd/system ]; then - systemctl --system daemon-reload >/dev/null || true - else - invoke-rc.d yunohost-api start || exit $? - fi -fi - #DEBHELPER# exit 0 diff --git a/debian/postrm b/debian/postrm index 2a711b136..2bbdd496b 100644 --- a/debian/postrm +++ b/debian/postrm @@ -3,7 +3,6 @@ set -e if [ "$1" = "purge" ]; then - update-rc.d yunohost-api remove >/dev/null update-rc.d yunohost-firewall remove >/dev/null fi diff --git a/debian/prerm b/debian/prerm index 85f17dde6..35486ea2d 100644 --- a/debian/prerm +++ b/debian/prerm @@ -3,7 +3,6 @@ set -e if [ -x /etc/init.d/yunohost-api ] && ! [ -d /run/systemd/system ]; then - invoke-rc.d yunohost-api stop || exit $? invoke-rc.d yunohost-firewall stop || true fi diff --git a/debian/rules b/debian/rules index 8d280ff77..ce03d0e31 100755 --- a/debian/rules +++ b/debian/rules @@ -8,12 +8,14 @@ dh ${@} --with=python2,systemd override_dh_installinit: - dh_installinit -pyunohost --name=yunohost-api --noscripts + dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade dh_installinit -pyunohost --name=yunohost-firewall --noscripts override_dh_systemd_enable: - dh_systemd_enable --name=yunohost-api - dh_systemd_enable --name=yunohost-firewall --no-enable + dh_systemd_enable --name=yunohost-api \ + yunohost-api.service + dh_systemd_enable --name=yunohost-firewall --no-enable \ + yunohost-firewall.service -override_dh_systemd_start: - dh_systemd_start --restart-after-upgrade yunohost-api.service +#override_dh_systemd_start: +# dh_systemd_start --restart-after-upgrade yunohost-api.service diff --git a/debian/yunohost-firewall.service b/debian/yunohost-firewall.service index f61ca1f64..1dd46f477 100644 --- a/debian/yunohost-firewall.service +++ b/debian/yunohost-firewall.service @@ -9,3 +9,6 @@ ExecStart=/usr/bin/yunohost firewall reload ExecReload=/usr/bin/yunohost firewall reload ExecStop=/usr/bin/yunohost firewall stop RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/locales/de.json b/locales/de.json index 519584426..b5a33ba26 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,213 +1,213 @@ { - "action_invalid": "Ungültige Aktion '{:s}'", - "admin_password": "Verwaltungspasswort", - "admin_password_change_failed": "Passwort kann nicht geändert werden", - "admin_password_changed": "Verwaltungspasswort wurde erfolgreich geändert", - "app_already_installed": "{:s} ist schon installiert", - "app_argument_choice_invalid": "Invalide Auswahl für Argument '{name:s}'. Muss einer der folgenden Werte sein {choices:s}", - "app_argument_invalid": "Das Argument '{name:s}' hat einen falschen Wert: {error:s}", - "app_argument_required": "Argument '{name:s}' wird benötigt", - "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", - "app_id_invalid": "Falsche App ID", - "app_install_files_invalid": "Ungültige Installationsdateien", - "app_location_already_used": "Eine andere App ist bereits an diesem Ort installiert", - "app_location_install_failed": "Die App kann an diesem Ort nicht installiert werden", - "app_manifest_invalid": "Ungültiges App Manifest", - "app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar", - "app_not_installed": "{:s} ist nicht intalliert", - "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", - "app_removed": "{:s} wurde erfolgreich entfernt", - "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden", - "app_unknown": "Unbekannte App", - "app_upgrade_failed": "Apps konnten nicht aktualisiert werden", - "app_upgraded": "{:s} wurde erfolgreich aktualisiert", - "appslist_fetched": "Liste der Apps wurde erfolgreich heruntergelanden", - "appslist_removed": "Appliste erfolgreich entfernt", - "appslist_retrieve_error": "Entfernte App Liste kann nicht gezogen werden", - "appslist_unknown": "Unbekannte App Liste", - "ask_current_admin_password": "Derzeitiges Verwaltungspasswort", - "ask_email": "E-Mail Adresse", - "ask_firstname": "Vorname", - "ask_lastname": "Nachname", - "ask_list_to_remove": "Liste enternen", - "ask_main_domain": "Hauptdomain", - "ask_new_admin_password": "Neues Verwaltungskennwort", - "ask_password": "Passwort", - "backup_action_required": "Du musst etwas zum Speichern auswählen", - "backup_app_failed": "Konnte keine Sicherung für '{app:s}' erstellen", - "backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden", - "backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden", - "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits", - "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", - "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", - "backup_cleaning_failed": "Verzeichnis von temporäre Sicherungsdaten konnte nicht geleert werden", - "backup_complete": "Datensicherung komplett", - "backup_creating_archive": "Datensicherung wird erstellt...", - "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", - "backup_deleted": "Datensicherung erfolgreich gelöscht", - "backup_extracting_archive": "Entpacke Sicherungsarchiv...", - "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", - "backup_invalid_archive": "Ungültige Datensicherung", - "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", - "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis", - "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", - "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", - "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", - "backup_running_hooks": "Datensicherunghook wird ausgeführt...", - "custom_app_url_required": "Es muss eine URL angegeben um deine benutzerdefinierte App {:s} zu aktualisieren", - "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", - "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", - "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", - "domain_created": "Domain erfolgreich erzeugt", - "domain_creation_failed": "Konnte Domain nicht erzeugen", - "domain_deleted": "Domain erfolgreich gelöscht", - "domain_deletion_failed": "Konnte Domain nicht löschen", - "domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-Domain angemeldet", - "domain_dyndns_invalid": "Domain nicht mittels DynDNS nutzbar", - "domain_dyndns_root_unknown": "Unbekannte DynDNS Hauptdomain", - "domain_exists": "Die Domain existiert bereits", - "domain_uninstall_app_first": "Mindestens eine App ist noch für diese Domain installiert. Bitte zuerst die App deinstallieren und erst dann die Domain löschen..", - "domain_unknown": "Unbekannte Domain", - "domain_zone_exists": "DNS Zonen Datei existiert bereits", - "domain_zone_not_found": "DNS Zonen Datei kann nicht für Domäne {:s} gefunden werden", - "done": "Erledigt.", - "downloading": "Wird heruntergeladen...", - "dyndns_cron_installed": "DynDNS Cronjob erfolgreich installiert", - "dyndns_cron_remove_failed": "DynDNS Cronjob konnte nicht entfernt werden", - "dyndns_cron_removed": "DynDNS Cronjob wurde erfolgreich gelöscht", - "dyndns_ip_update_failed": "IP Adresse konnte nicht für DynDNS aktualisiert werden", - "dyndns_ip_updated": "IP Adresse wurde erfolgreich für DynDNS aktualisiert", - "dyndns_key_generating": "DNS Schlüssel wird generiert, das könnte eine Weile dauern...", - "dyndns_registered": "DynDNS Domain erfolgreich registriert", - "dyndns_registration_failed": "DynDNS Domain {:s} konnte nicht registriert werden", - "dyndns_unavailable": "DynDNS Subdomain ist nicht verfügbar", - "executing_command": "Führe Kommendo '{command:s}' aus...", - "executing_script": "Skript '{script:s}' wird ausgeührt...", - "extracting": "Wird entpackt...", - "field_invalid": "Feld '{:s}' ist unbekannt", - "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", - "firewall_reloaded": "Firewall erfolgreich neu geladen", - "firewall_rules_cmd_failed": "Einzelne Firewallregeln konnten nicht übernommen werden. Mehr Informationen sind im Log zu finden.", - "format_datetime_short": "%m/%d/%Y %I:%M %p", - "hook_argument_missing": "Fehlend Argument '{:s}'", - "hook_choice_invalid": "ungültige Wahl '{:s}'", - "hook_exec_failed": "Skriptausführung fehlgeschlagen", - "hook_exec_not_terminated": "Skriptausführung noch nicht beendet", - "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", - "hook_name_unknown": "Hook '{:s}' ist nicht bekannt", - "installation_complete": "Installation vollständig", - "installation_failed": "Installation fehlgeschlagen", - "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container, oder es wird nicht vom Kernel unterstützt.", - "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container, oder es wird nicht vom Kernel unterstützt.", - "ldap_initialized": "LDAP erfolgreich initialisiert", - "license_undefined": "Undeiniert", - "mail_alias_remove_failed": "E-Mail Alias '{:s}' konnte nicht entfernt werden", - "mail_domain_unknown": "Unbekannte Mail Domain '{:s}'", - "mail_forward_remove_failed": "Mailweiterleitung '{:s}' konnte nicht entfernt werden", - "maindomain_change_failed": "Hauptdomain konnte nicht geändert werden", - "maindomain_changed": "Hauptdomain wurde erfolgreich geändert", - "monitor_disabled": "Servermonitoring erfolgreich deaktiviert", - "monitor_enabled": "Servermonitoring erfolgreich aktiviert", - "monitor_glances_con_failed": "Verbindung mit Glances nicht möglich", - "monitor_not_enabled": "Servermonitoring ist nicht aktiviert", - "monitor_period_invalid": "Falscher Zeitraum", - "monitor_stats_file_not_found": "Statistikdatei nicht gefunden", - "monitor_stats_no_update": "Keine Monitoringstatistik zur Aktualisierung", - "monitor_stats_period_unavailable": "Keine Statistiken für den gewählten Zeitraum verfügbar", - "mountpoint_unknown": "Unbekannten Einhängepunkt", - "mysql_db_creation_failed": "MySQL Datenbankerzeugung fehlgeschlagen", - "mysql_db_init_failed": "MySQL Datenbankinitialisierung fehlgeschlagen", - "mysql_db_initialized": "MySQL Datenbank erfolgreich initialisiert", - "network_check_mx_ko": "Es ist kein DNS MX Eintrag vorhanden", - "network_check_smtp_ko": "Ausgehender Mailverkehr (SMTP Port 25) scheint in deinem Netzwerk blockiert zu sein", - "network_check_smtp_ok": "Ausgehender Mailverkehr (SMTP Port 25) ist blockiert", - "new_domain_required": "Du musst eine neue Hauptdomain angeben", - "no_appslist_found": "Keine Appliste gefunden", - "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", - "no_ipv6_connectivity": "Eine IPv6 Verbindung steht nicht zur Verfügung", - "no_restore_script": "Es konnte kein Wiederherstellungsskript für '{app:s}' gefunden werden", - "no_such_conf_file": "Datei {file:s}: konnte nicht kopiert werden, da diese nicht existiert", - "packages_no_upgrade": "Es müssen keine Pakete aktualisiert werden", - "packages_upgrade_critical_later": "Wichtiges Paket ({:s}) wird später aktualisiert", - "packages_upgrade_failed": "Es konnten nicht alle Pakete aktualisiert werden", - "path_removal_failed": "Pfad {:s} konnte nicht entfernt werden", - "pattern_backup_archive_name": "Ein gültiger Dateiname kann nur aus alphanumerischen und -_. bestehen", - "pattern_domain": "Muss ein gültiger Domainname sein (z.B. meine-domain.org)", - "pattern_email": "Muss eine gültige E-Mail Adresse sein (z.B. someone@domain.org)", - "pattern_firstname": "Muss ein gültiger Vorname sein", - "pattern_lastname": "Muss ein gültiger Nachname sein", - "pattern_listname": "Kann nur Alphanumerische Zeichen oder Unterstriche enthalten", - "pattern_mailbox_quota": "Muss eine Größe inkl. b/k/M/G/T Suffix, oder 0 zum deaktivieren sein", - "pattern_password": "Muss mindestens drei Zeichen lang sein", - "pattern_port": "Es muss ein valider Port (zwischen 0 und 65535) angegeben werden", - "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", - "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", - "port_already_closed": "Port {} wurde bereits für {:s} Verbindungen geschlossen", - "port_already_opened": "Der Port {} wird bereits von {:s} benutzt", - "port_available": "Port {} ist verfügbar", - "port_unavailable": "Der Port {} ist nicht verfügbar", - "restore_action_required": "Du musst etwas zum Wiederherstellen auswählen", - "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", - "restore_app_failed": "App '{app:s}' konnte nicht wiederhergestellt werden", - "restore_cleaning_failed": "Temporäres Wiederherstellungsverzeichnis konnte nicht geleert werden", - "restore_complete": "Wiederherstellung abgeschlossen", - "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", - "restore_failed": "System kann nicht Wiederhergestellt werden", - "restore_hook_unavailable": "Der Wiederherstellungshook '{hook:s}' steht auf deinem System nicht zur Verfügung", - "restore_nothings_done": "Es wurde nicht wiederhergestellt", - "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", - "restore_running_hooks": "Wiederherstellung wird gestartet...", - "service_add_configuration": "Füge Konfigurationsdatei {file:s} hinzu", - "service_add_failed": "Dienst '{:s}' kann nicht hinzugefügt werden", - "service_added": "Service erfolgreich hinzugefügt", - "service_already_started": "Der Dienst '{:s}' läutt bereits", - "service_already_stopped": "Dienst '{:s}' wurde bereits gestoppt", - "service_cmd_exec_failed": "Kommando '{:s}' kann nicht ausgeführt werden", - "service_configuration_conflict": "Die Datei {file:s} wurde zwischenzeitlich verändert. Bitte übernehme die Änderungen manuell oder nutze die Option --force (diese wird alle Änderungen überschreiben).", - "service_disable_failed": "Dienst'{:s}' konnte nicht deaktiviert werden", - "service_disabled": "Der Dienst '{:s}' wurde erfolgreich deaktiviert", - "service_enable_failed": "Dienst '{:s}' konnte nicht aktiviert werden", - "service_enabled": "Dienst '{:s}' erfolgreich aktiviert", - "service_no_log": "Für den Dienst '{:s}' kann kein Log angezeigt werden", - "service_remove_failed": "Dienst '{:s}' konnte nicht entfernt werden", - "service_removed": "Dienst erfolgreich enternt", - "service_start_failed": "Dienst '{:s}' konnte nicht gestartet werden", - "service_started": "der Dienst '{:s}' wurde erfolgreich gestartet", - "service_status_failed": "Status von '{:s}' kann nicht festgestellt werden", - "service_stop_failed": "Dienst '{:s}' kann nicht gestoppt werden", - "service_stopped": "Dienst '{:s}' wurde erfolgreich beendet", - "service_unknown": "Unbekannte Dienst '{:s}'", - "services_configured": "Konfiguration erfolgreich erstellt", - "show_diff": "Es gibt folgende Änderungen:\n{diff:s}", - "ssowat_conf_generated": "Konfiguration von SSOwat erfolgreich", - "ssowat_conf_updated": "Persistente SSOwat Einstellung erfolgreich aktualisiert", - "system_upgraded": "System wurde erfolgreich aktualisiert", - "system_username_exists": "Der Benutzername existiert bereits", - "unbackup_app": "App '{app:s}' konnte nicht gespeichert werden", - "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten", - "unit_unknown": "Unbekannte Einheit '{:s}'", - "unlimit": "Kein Kontingent", - "unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden", - "update_cache_failed": "Konnte APT cache nicht aktualisieren", - "updating_apt_cache": "Liste der verfügbaren Pakete wird aktualisiert...", - "upgrade_complete": "Upgrade vollständig", - "upgrading_packages": "Pakete werden aktualisiert...", - "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", - "upnp_disabled": "UPnP wurde erfolgreich deaktiviert", - "upnp_enabled": "UPnP wurde aktiviert", - "upnp_port_open_failed": "UPnP Ports konnten nicht geöffnet werden", - "user_created": "Benutzer erfolgreich erstellt", - "user_creation_failed": "Nutzer konnte nicht erstellt werden", - "user_deleted": "Benutzer wurde erfolgreich entfernt", - "user_deletion_failed": "Nutzer konnte nicht gelöscht werden", - "user_home_creation_failed": "Benutzer Home konnte nicht erstellt werden", - "user_info_failed": "Nutzerinformationen können nicht angezeigt werden", - "user_unknown": "Unbekannter Benutzer", - "user_update_failed": "Benutzer kann nicht aktualisiert werden", - "user_updated": "Benutzer wurde erfolgreich aktualisiert", - "yunohost_already_installed": "YunoHost ist bereits installiert", - "yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden", - "yunohost_configured": "YunoHost wurde erfolgreich konfiguriert", - "yunohost_installing": "YunoHost wird installiert...", + "action_invalid": "Ungültige Aktion '{action:s}'", + "admin_password": "Verwaltungspasswort", + "admin_password_change_failed": "Passwort kann nicht geändert werden", + "admin_password_changed": "Verwaltungspasswort wurde erfolgreich geändert", + "app_already_installed": "{app:s} ist schon installiert", + "app_argument_choice_invalid": "Invalide Auswahl für Argument '{name:s}'. Muss einer der folgenden Werte sein {choices:s}", + "app_argument_invalid": "Das Argument '{name:s}' hat einen falschen Wert: {error:s}", + "app_argument_required": "Argument '{name:s}' wird benötigt", + "app_extraction_failed": "Installationsdateien konnten nicht entpackt werden", + "app_id_invalid": "Falsche App ID", + "app_install_files_invalid": "Ungültige Installationsdateien", + "app_location_already_used": "Eine andere App ist bereits an diesem Ort installiert", + "app_location_install_failed": "Die App kann an diesem Ort nicht installiert werden", + "app_manifest_invalid": "Ungültiges App Manifest", + "app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar", + "app_not_installed": "{app:s} ist nicht intalliert", + "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", + "app_removed": "{app:s} wurde erfolgreich entfernt", + "app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden", + "app_unknown": "Unbekannte App", + "app_upgrade_failed": "Apps konnten nicht aktualisiert werden", + "app_upgraded": "{app:s} wurde erfolgreich aktualisiert", + "appslist_fetched": "Liste der Apps wurde erfolgreich heruntergelanden", + "appslist_removed": "Appliste erfolgreich entfernt", + "appslist_retrieve_error": "Entfernte App Liste kann nicht gezogen werden", + "appslist_unknown": "Unbekannte App Liste", + "ask_current_admin_password": "Derzeitiges Verwaltungspasswort", + "ask_email": "E-Mail Adresse", + "ask_firstname": "Vorname", + "ask_lastname": "Nachname", + "ask_list_to_remove": "Liste enternen", + "ask_main_domain": "Hauptdomain", + "ask_new_admin_password": "Neues Verwaltungskennwort", + "ask_password": "Passwort", + "backup_action_required": "Du musst etwas zum Speichern auswählen", + "backup_app_failed": "Konnte keine Sicherung für '{app:s}' erstellen", + "backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden", + "backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden", + "backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits", + "backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden", + "backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen", + "backup_cleaning_failed": "Verzeichnis von temporäre Sicherungsdaten konnte nicht geleert werden", + "backup_complete": "Datensicherung komplett", + "backup_creating_archive": "Datensicherung wird erstellt...", + "backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden", + "backup_deleted": "Datensicherung erfolgreich gelöscht", + "backup_extracting_archive": "Entpacke Sicherungsarchiv...", + "backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt", + "backup_invalid_archive": "Ungültige Datensicherung", + "backup_nothings_done": "Es gibt keine Änderungen zur Speicherung", + "backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis", + "backup_output_directory_not_empty": "Ausgabeordner ist nicht leer", + "backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden", + "backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...", + "backup_running_hooks": "Datensicherunghook wird ausgeführt...", + "custom_app_url_required": "Es muss eine URL angegeben um deine benutzerdefinierte App {app:s} zu aktualisieren", + "custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben", + "dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus", + "domain_cert_gen_failed": "Zertifikat konnte nicht erzeugt werden", + "domain_created": "Domain erfolgreich erzeugt", + "domain_creation_failed": "Konnte Domain nicht erzeugen", + "domain_deleted": "Domain erfolgreich gelöscht", + "domain_deletion_failed": "Konnte Domain nicht löschen", + "domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-Domain angemeldet", + "domain_dyndns_invalid": "Domain nicht mittels DynDNS nutzbar", + "domain_dyndns_root_unknown": "Unbekannte DynDNS Hauptdomain", + "domain_exists": "Die Domain existiert bereits", + "domain_uninstall_app_first": "Mindestens eine App ist noch für diese Domain installiert. Bitte zuerst die App deinstallieren und erst dann die Domain löschen..", + "domain_unknown": "Unbekannte Domain", + "domain_zone_exists": "DNS Zonen Datei existiert bereits", + "domain_zone_not_found": "DNS Zonen Datei kann nicht für Domäne {:s} gefunden werden", + "done": "Erledigt.", + "downloading": "Wird heruntergeladen...", + "dyndns_cron_installed": "DynDNS Cronjob erfolgreich installiert", + "dyndns_cron_remove_failed": "DynDNS Cronjob konnte nicht entfernt werden", + "dyndns_cron_removed": "DynDNS Cronjob wurde erfolgreich gelöscht", + "dyndns_ip_update_failed": "IP Adresse konnte nicht für DynDNS aktualisiert werden", + "dyndns_ip_updated": "IP Adresse wurde erfolgreich für DynDNS aktualisiert", + "dyndns_key_generating": "DNS Schlüssel wird generiert, das könnte eine Weile dauern...", + "dyndns_registered": "DynDNS Domain erfolgreich registriert", + "dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}", + "dyndns_unavailable": "DynDNS Subdomain ist nicht verfügbar", + "executing_command": "Führe Kommendo '{command:s}' aus...", + "executing_script": "Skript '{script:s}' wird ausgeührt...", + "extracting": "Wird entpackt...", + "field_invalid": "Feld '{:s}' ist unbekannt", + "firewall_reload_failed": "Firewall konnte nicht neu geladen werden", + "firewall_reloaded": "Firewall erfolgreich neu geladen", + "firewall_rules_cmd_failed": "Einzelne Firewallregeln konnten nicht übernommen werden. Mehr Informationen sind im Log zu finden.", + "format_datetime_short": "%m/%d/%Y %I:%M %p", + "hook_argument_missing": "Fehlend Argument '{:s}'", + "hook_choice_invalid": "ungültige Wahl '{:s}'", + "hook_exec_failed": "Skriptausführung fehlgeschlagen", + "hook_exec_not_terminated": "Skriptausführung noch nicht beendet", + "hook_list_by_invalid": "Ungültiger Wert zur Anzeige von Hooks", + "hook_name_unknown": "Hook '{name:s}' ist nicht bekannt", + "installation_complete": "Installation vollständig", + "installation_failed": "Installation fehlgeschlagen", + "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container, oder es wird nicht vom Kernel unterstützt.", + "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container, oder es wird nicht vom Kernel unterstützt.", + "ldap_initialized": "LDAP erfolgreich initialisiert", + "license_undefined": "Undeiniert", + "mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden", + "mail_domain_unknown": "Unbekannte Mail Domain '{domain:s}'", + "mail_forward_remove_failed": "Mailweiterleitung '{mail:s}' konnte nicht entfernt werden", + "maindomain_change_failed": "Hauptdomain konnte nicht geändert werden", + "maindomain_changed": "Hauptdomain wurde erfolgreich geändert", + "monitor_disabled": "Servermonitoring erfolgreich deaktiviert", + "monitor_enabled": "Servermonitoring erfolgreich aktiviert", + "monitor_glances_con_failed": "Verbindung mit Glances nicht möglich", + "monitor_not_enabled": "Servermonitoring ist nicht aktiviert", + "monitor_period_invalid": "Falscher Zeitraum", + "monitor_stats_file_not_found": "Statistikdatei nicht gefunden", + "monitor_stats_no_update": "Keine Monitoringstatistik zur Aktualisierung", + "monitor_stats_period_unavailable": "Keine Statistiken für den gewählten Zeitraum verfügbar", + "mountpoint_unknown": "Unbekannten Einhängepunkt", + "mysql_db_creation_failed": "MySQL Datenbankerzeugung fehlgeschlagen", + "mysql_db_init_failed": "MySQL Datenbankinitialisierung fehlgeschlagen", + "mysql_db_initialized": "MySQL Datenbank erfolgreich initialisiert", + "network_check_mx_ko": "Es ist kein DNS MX Eintrag vorhanden", + "network_check_smtp_ko": "Ausgehender Mailverkehr (SMTP Port 25) scheint in deinem Netzwerk blockiert zu sein", + "network_check_smtp_ok": "Ausgehender Mailverkehr (SMTP Port 25) ist blockiert", + "new_domain_required": "Du musst eine neue Hauptdomain angeben", + "no_appslist_found": "Keine Appliste gefunden", + "no_internet_connection": "Der Server ist nicht mit dem Internet verbunden", + "no_ipv6_connectivity": "Eine IPv6 Verbindung steht nicht zur Verfügung", + "no_restore_script": "Es konnte kein Wiederherstellungsskript für '{app:s}' gefunden werden", + "no_such_conf_file": "Datei {file:s}: konnte nicht kopiert werden, da diese nicht existiert", + "packages_no_upgrade": "Es müssen keine Pakete aktualisiert werden", + "packages_upgrade_critical_later": "Wichtiges Paket ({packages:s}) wird später aktualisiert", + "packages_upgrade_failed": "Es konnten nicht alle Pakete aktualisiert werden", + "path_removal_failed": "Pfad {:s} konnte nicht entfernt werden", + "pattern_backup_archive_name": "Ein gültiger Dateiname kann nur aus alphanumerischen und -_. bestehen", + "pattern_domain": "Muss ein gültiger Domainname sein (z.B. meine-domain.org)", + "pattern_email": "Muss eine gültige E-Mail Adresse sein (z.B. someone@domain.org)", + "pattern_firstname": "Muss ein gültiger Vorname sein", + "pattern_lastname": "Muss ein gültiger Nachname sein", + "pattern_listname": "Kann nur Alphanumerische Zeichen oder Unterstriche enthalten", + "pattern_mailbox_quota": "Muss eine Größe inkl. b/k/M/G/T Suffix, oder 0 zum deaktivieren sein", + "pattern_password": "Muss mindestens drei Zeichen lang sein", + "pattern_port": "Es muss ein valider Port (zwischen 0 und 65535) angegeben werden", + "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", + "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", + "port_already_closed": "Port {port:d} wurde bereits für {ip_version:s} Verbindungen geschlossen", + "port_already_opened": "Der Port {port:d} wird bereits von {ip_version:s} benutzt", + "port_available": "Port {port:d} ist verfügbar", + "port_unavailable": "Der Port {port:d} ist nicht verfügbar", + "restore_action_required": "Du musst etwas zum Wiederherstellen auswählen", + "restore_already_installed_app": "Es ist bereits eine App mit der ID '{app:s}' installiet", + "restore_app_failed": "App '{app:s}' konnte nicht wiederhergestellt werden", + "restore_cleaning_failed": "Temporäres Wiederherstellungsverzeichnis konnte nicht geleert werden", + "restore_complete": "Wiederherstellung abgeschlossen", + "restore_confirm_yunohost_installed": "Möchtest du die Wiederherstellung wirklich starten? [{answers:s}]", + "restore_failed": "System kann nicht Wiederhergestellt werden", + "restore_hook_unavailable": "Der Wiederherstellungshook '{hook:s}' steht auf deinem System nicht zur Verfügung", + "restore_nothings_done": "Es wurde nicht wiederhergestellt", + "restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...", + "restore_running_hooks": "Wiederherstellung wird gestartet...", + "service_add_configuration": "Füge Konfigurationsdatei {file:s} hinzu", + "service_add_failed": "Dienst '{service:s}' kann nicht hinzugefügt werden", + "service_added": "Service erfolgreich hinzugefügt", + "service_already_started": "Der Dienst '{service:s}' läutt bereits", + "service_already_stopped": "Dienst '{service:s}' wurde bereits gestoppt", + "service_cmd_exec_failed": "Kommando '{command:s}' kann nicht ausgeführt werden", + "service_configuration_conflict": "Die Datei {file:s} wurde zwischenzeitlich verändert. Bitte übernehme die Änderungen manuell oder nutze die Option --force (diese wird alle Änderungen überschreiben).", + "service_disable_failed": "Dienst '{service:s}' konnte nicht deaktiviert werden", + "service_disabled": "Der Dienst '{service:s}' wurde erfolgreich deaktiviert", + "service_enable_failed": "Dienst '{service:s}' konnte nicht aktiviert werden", + "service_enabled": "Dienst '{service:s}' erfolgreich aktiviert", + "service_no_log": "Für den Dienst '{service:s}' kann kein Log angezeigt werden", + "service_remove_failed": "Dienst '{service:s}' konnte nicht entfernt werden", + "service_removed": "Dienst erfolgreich enternt", + "service_start_failed": "Dienst '{service:s}' konnte nicht gestartet werden", + "service_started": "der Dienst '{service:s}' wurde erfolgreich gestartet", + "service_status_failed": "Status von '{service:s}' kann nicht festgestellt werden", + "service_stop_failed": "Dienst '{service:s}' kann nicht gestoppt werden", + "service_stopped": "Dienst '{service:s}' wurde erfolgreich beendet", + "service_unknown": "Unbekannte Dienst '{service:s}'", + "services_configured": "Konfiguration erfolgreich erstellt", + "show_diff": "Es gibt folgende Änderungen:\n{diff:s}", + "ssowat_conf_generated": "Konfiguration von SSOwat erfolgreich", + "ssowat_conf_updated": "Persistente SSOwat Einstellung erfolgreich aktualisiert", + "system_upgraded": "System wurde erfolgreich aktualisiert", + "system_username_exists": "Der Benutzername existiert bereits", + "unbackup_app": "App '{app:s}' konnte nicht gespeichert werden", + "unexpected_error": "Ein unerwarteter Fehler ist aufgetreten", + "unit_unknown": "Unbekannte Einheit '{unit:s}'", + "unlimit": "Kein Kontingent", + "unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden", + "update_cache_failed": "Konnte APT cache nicht aktualisieren", + "updating_apt_cache": "Liste der verfügbaren Pakete wird aktualisiert...", + "upgrade_complete": "Upgrade vollständig", + "upgrading_packages": "Pakete werden aktualisiert...", + "upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden", + "upnp_disabled": "UPnP wurde erfolgreich deaktiviert", + "upnp_enabled": "UPnP wurde aktiviert", + "upnp_port_open_failed": "UPnP Ports konnten nicht geöffnet werden", + "user_created": "Benutzer erfolgreich erstellt", + "user_creation_failed": "Nutzer konnte nicht erstellt werden", + "user_deleted": "Benutzer wurde erfolgreich entfernt", + "user_deletion_failed": "Nutzer konnte nicht gelöscht werden", + "user_home_creation_failed": "Benutzer Home konnte nicht erstellt werden", + "user_info_failed": "Nutzerinformationen können nicht angezeigt werden", + "user_unknown": "Unbekannter Benutzer", + "user_update_failed": "Benutzer kann nicht aktualisiert werden", + "user_updated": "Benutzer wurde erfolgreich aktualisiert", + "yunohost_already_installed": "YunoHost ist bereits installiert", + "yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden", + "yunohost_configured": "YunoHost wurde erfolgreich konfiguriert", + "yunohost_installing": "YunoHost wird installiert...", "yunohost_not_installed": "Die YunoHost ist unvollständig. Bitte 'yunohost tools postinstall' ausführen." -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index 10c056d9b..9290b7c24 100644 --- a/locales/en.json +++ b/locales/en.json @@ -22,7 +22,7 @@ "custom_app_url_required" : "You must provide an URL to upgrade your custom app {app:s}", "app_requirements_checking" : "Checking required packages...", "app_requirements_unmeet" : "Requirements are not met, the package {pkgname} ({version}) must be {spec}", - "app_requirements_failed" : "Unable to meet requirements: {err}", + "app_requirements_failed" : "Unable to meet requirements: {error}", "app_upgraded" : "{app:s} successfully upgraded", "app_upgrade_failed" : "Unable to upgrade {app:s}", "app_id_invalid" : "Invalid app id", @@ -34,6 +34,8 @@ "app_extraction_failed" : "Unable to extract installation files", "app_install_files_invalid" : "Invalid installation files", "app_manifest_invalid" : "Invalid app manifest", + "app_incompatible" : "The app is incompatible with your YunoHost version", + "app_package_need_update" : "The app package need to be updated to follow YunoHost changes", "app_argument_choice_invalid" : "Invalid choice for argument '{name:s}', it must be one of {choices:s}", "app_argument_invalid" : "Invalid value for argument '{name:s}': {error:s}", "app_argument_required" : "Argument '{name:s}' is required", @@ -128,12 +130,20 @@ "service_status_failed" : "Unable to determine status of service '{service:s}'", "service_no_log" : "No log to display for service '{service:s}'", "service_cmd_exec_failed" : "Unable to execute command '{command:s}'", - "service_configured": "Configuration successfully generated for service '{service:s}'", - "service_configured_all": "Configuration successfully generated for every services", - "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file).", - "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", - "service_add_configuration": "Adding the configuration file {file:s}", - "show_diff": "Here are the differences:\n{diff:s}", + "service_regenconf_failed" : "Unable to regenerate the configuration for service(s): {services}", + "service_regenconf_pending_applying" : "Applying pending configuration for service '{service}'...", + "service_regenconf_dry_pending_applying" : "Checking pending configuration which would have been applied for service '{service}'...", + "service_conf_file_manually_removed" : "The configuration file '{conf}' has been manually removed and will not be created", + "service_conf_file_manually_modified" : "The configuration file '{conf}' has been manually modified and will not be updated", + "service_conf_file_not_managed" : "The configuration file '{conf}' is not managed yet and will not be updated", + "service_conf_file_backed_up" : "The configuration file '{conf}' has been backed up to '{backup}'", + "service_conf_file_removed" : "The configuration file '{conf}' has been removed", + "service_conf_file_remove_failed" : "Unable to remove the configuration file '{conf}'", + "service_conf_file_updated" : "The configuration file '{conf}' has been updated", + "service_conf_file_copy_failed" : "Unable to copy the new configuration file '{new}' to '{conf}'", + "service_conf_up_to_date" : "The configuration is already up-to-date 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}'", "network_check_smtp_ok" : "Outbound mail (SMTP port 25) is not blocked", "network_check_smtp_ko" : "Outbound mail (SMTP port 25) seems to be blocked by your network", diff --git a/locales/es.json b/locales/es.json index 7eaf09dd9..c38e496fc 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,9 +1,9 @@ { - "action_invalid": "Acción inválida '{:s}'", + "action_invalid": "Acción inválida '{action:s}'", "admin_password": "Contraseña administrativa", "admin_password_change_failed": "No se pudo cambiar la contraseña", "admin_password_changed": "Contraseña administrativa se cambió con éxito", - "app_already_installed": "{:s} ya está instalado ", + "app_already_installed": "{app:s} ya está instalado", "app_extraction_failed": "No se pudo extraer los archivos de instalación ", "app_id_invalid": "id de la aplicación inválida ", "app_install_files_invalid": "Archivos de instalación inválidos ", @@ -11,13 +11,13 @@ "app_location_install_failed": "No se pudo instalar la aplicación en esta lugar", "app_manifest_invalid": "Manifesto de la aplicación es inválido", "app_no_upgrade": "Ninguna app a actualizar", - "app_not_installed": "{:s} no está instalado.", + "app_not_installed": "{app:s} no está instalado", "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", - "app_removed": "{:s} era eliminado con éxito ", + "app_removed": "{app:s} era eliminado con éxito", "app_sources_fetch_failed": "No se pudo descargar los archivos de códigos fuentes", "app_unknown": "App desconocida", "app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ", - "app_upgraded": "{:s} actualizado con éxito", + "app_upgraded": "{app:s} actualizado con éxito", "appslist_fetched": "Lista de aplicaciones se trajo con éxito", "appslist_removed": "Lista de aplicaciones se eliminó con éxito", "appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ", @@ -41,7 +41,7 @@ "backup_output_directory_not_empty": "La carpeta de salida no está vacía", "backup_output_directory_required": "Debe proporcionar un directorio de salida para el backup", "backup_running_hooks": "Ejecutando los hooks de backup...", - "custom_app_url_required": " Debe proporcionar una URL para actualizar su aplicación personalizada {:s} ", + "custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}", "custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, por favor, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cert_gen_failed": "No se pudo crear certificado", @@ -66,7 +66,7 @@ "dyndns_ip_updated": "La dirección IP era actualizado en DynDNS con éxito", "dyndns_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...", "dyndns_registered": "El dominio DynDNS era registrado con éxito.", - "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}", + "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}", "dyndns_unavailable": "Subdominio DynDNS no disponible", "executing_script": "Ejecutando script...", "extracting": "Extrayendo...", @@ -77,16 +77,16 @@ "hook_argument_missing": "Falta un parámetro '{:s}'", "hook_choice_invalid": "Selección inválida '{:s}'", "hook_list_by_invalid": "La propiedad de este hook es inválida", - "hook_name_unknown": "Hook desconocido '{:s}'", + "hook_name_unknown": "Hook desconocido '{name:s}'", "installation_complete": "La instalación se ha completado", "installation_failed": "La Instalación se ha fracasado", "ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.", "iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.", "ldap_initialized": "LDAP se inició con éxito", "license_undefined": "indefinido", - "mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'", - "mail_domain_unknown": "El dominio de correos '{:s}' es desconocido", - "mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{:s}'", + "mail_alias_remove_failed": "No se pudo quitar el alias de correos '{mail:s}'", + "mail_domain_unknown": "El dominio de correos '{domain:s}' es desconocido", + "mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{mail:s}'", "maindomain_change_failed": "No se pudo cambiar el dominio principal", "maindomain_changed": "Dominio principal se cambió con éxito", "monitor_disabled": "Supervisión del sistema era desactivado con éxito", @@ -105,7 +105,7 @@ "no_appslist_found": "No se encontró ninguna lista de Apps", "no_internet_connection": "El servidor no está conectado al Internet.", "packages_no_upgrade": "No hay actualización por ningun paquete", - "packages_upgrade_critical_later": "Los paquetes críticos ({:s}) se actualizarán más tarde", + "packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) se actualizarán más tarde", "packages_upgrade_failed": "No se pudo actualizar todo de los paquetes", "path_removal_failed": "No se pudo quitar la ruta {:s}", "pattern_backup_archive_name": "Debe que ser un nombre de archivo válido con los caracteres alfanumericos, o los -_.", @@ -118,39 +118,39 @@ "pattern_port": "El numéro del puerto debe ser válido (i.e. 0-65535)", "pattern_port_or_range": "El numéro del puerto debe ser válido (i.e. 0-65535) o un intervalo de puertos (e.g. 100:200)", "pattern_username": "Debe contener solamente caracteres alfanuméricos o la guion bajo", - "port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.", - "port_already_opened": "El puerto {} ya está abierto por {:s} connecciones", - "port_available": "El puerto {} está disponible", - "port_unavailable": "El puerto {} no está disponible", + "port_already_closed": "El puerto {port:d} ya está cerrado por {ip_version:s} connecciones", + "port_already_opened": "El puerto {port:d} ya está abierto por {ip_version:s} connecciones", + "port_available": "El puerto {port:d} está disponible", + "port_unavailable": "El puerto {port:d} no está disponible", "restore_complete": "Restauración se ha completado", "restore_confirm_yunohost_installed": "Estás seguro que quieres restaurar a un sistema que ya está instalado? [{answers:s}]", "restore_failed": "No se pudo restaurar el sistema", "restore_running_hooks": "Ejecutando hooks de restauración...", - "service_add_failed": "No se pudo añadir el servicio '{:s}'", + "service_add_failed": "No se pudo añadir el servicio '{service:s}'", "service_added": "Servicio añadido con éxito", - "service_already_started": "El servicio '{:s}' ya se ha empezado", - "service_already_stopped": "El servicio '{:s}' ya está parado ", - "service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'", - "service_disable_failed": "No se pudo desactivar el servicio '{:s}'", - "service_disabled": "Servicio '{:s}' desactivado con éxito", - "service_enable_failed": "No se pudo activar el servicio '{:s}'", - "service_enabled": "Servicio '{:s}' activado con éxito", - "service_no_log": "No hay archivo historial del servicio '{:s}' a exhibir", - "service_remove_failed": "No se pudo quitar el servicio '{:s}'", + "service_already_started": "El servicio '{service:s}' ya se ha empezado", + "service_already_stopped": "El servicio '{service:s}' ya está parado", + "service_cmd_exec_failed": "No se pudo ejecutar comando '{command:s}'", + "service_disable_failed": "No se pudo desactivar el servicio '{service:s}'", + "service_disabled": "Servicio '{service:s}' desactivado con éxito", + "service_enable_failed": "No se pudo activar el servicio '{service:s}'", + "service_enabled": "Servicio '{service:s}' activado con éxito", + "service_no_log": "No hay archivo historial del servicio '{service:s}' a exhibir", + "service_remove_failed": "No se pudo quitar el servicio '{service:s}'", "service_removed": "Servicio quitado con éxito", - "service_start_failed": "No se pudo empezar el servicio '{:s}'", - "service_started": "El servicio '{:s}' se empezó con éxito", - "service_status_failed": "No se pudo discernir el estado del servicio '{:s}'", - "service_stop_failed": "No se pudo parar el servicio '{:s}'", - "service_stopped": "Servicio '{:s}' parado con éxito", - "service_unknown": "Servicio desconocido '{:s}'", + "service_start_failed": "No se pudo empezar el servicio '{service:s}'", + "service_started": "El servicio '{service:s}' se empezó con éxito", + "service_status_failed": "No se pudo discernir el estado del servicio '{service:s}'", + "service_stop_failed": "No se pudo parar el servicio '{service:s}'", + "service_stopped": "Servicio '{service:s}' parado con éxito", + "service_unknown": "Servicio desconocido '{service:s}'", "ssowat_conf_generated": "Configuración SSOwat generado con éxito ", "ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito", "system_upgraded": "Actualización del sistema se ha completado con éxito.", "system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema", "unbackup_app": "La App '{:s}' no será guardada", "unexpected_error": "Un error ha ocurrido", - "unit_unknown": "Unidad '{:s}' desconocido", + "unit_unknown": "Unidad '{unit:s}' desconocido", "unrestore_app": "La App '{:s}' no será restaurada", "update_cache_failed": "No se pudo actualizar el cache APT", "updating_apt_cache": "Actualizando la lista de paquetes disponibles...", diff --git a/locales/fr.json b/locales/fr.json index b483e111a..f0665d4f5 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,218 +1,235 @@ { - "action_invalid": "Action « {:s} » incorrecte", - "admin_password": "Mot de passe d'administration", - "admin_password_change_failed": "Impossible de modifier le mot de passe d'administration", - "admin_password_changed": "Mot de passe d'administration modifié avec succès", - "app_already_installed": "{app:s} est déjà installé", - "app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}", - "app_argument_invalid": "Valeur invalide pour le paramètre « {name:s} » : {error:s}", - "app_argument_missing": "Paramètre manquant « {:s} »", - "app_argument_required": "Le paramètre « {name:s} » est requis", - "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", - "app_id_invalid": "Id d'application incorrect", - "app_install_files_invalid": "Fichiers d'installation incorrects", - "app_location_already_used": "Une application est déjà installée à cet emplacement", - "app_location_install_failed": "Impossible d'installer l'application à cet emplacement", - "app_manifest_invalid": "Manifeste d'application incorrect", - "app_no_upgrade": "Aucune application à mettre à jour", - "app_not_correctly_installed": "{app:s} semble être mal installé", - "app_not_installed": "{app:s} n'est pas installé", - "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", - "app_removed": "{app:s} supprimé avec succès", - "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", - "app_unknown": "Application inconnue", - "app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté", - "app_upgrade_failed": "Impossible de mettre à jour {app:s}", - "app_upgraded": "{app:s} mis à jour avec succès", - "appslist_fetched": "Liste d'applications récupérée avec succès", - "appslist_removed": "Liste d'applications supprimée avec succès", - "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", - "appslist_unknown": "Liste d'applications inconnue", - "ask_current_admin_password": "Mot de passe d'administration actuel", - "ask_email": "Adresse courriel", - "ask_firstname": "Prénom", - "ask_lastname": "Nom", - "ask_list_to_remove": "Liste à supprimer", - "ask_main_domain": "Domaine principal", - "ask_new_admin_password": "Nouveau mot de passe d'administration", - "ask_password": "Mot de passe", - "backup_action_required": "Vous devez préciser ce qui est à sauvegarder", - "backup_app_failed": "Impossible de sauvegarder l'application « {app:s} »", - "backup_archive_app_not_found": "L'application « {app:s} » n'a pas été trouvée dans l'archive de la sauvegarde", - "backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde", - "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", - "backup_archive_name_unknown": "L'archive locale de sauvegarde nommée « {name:s} » est inconnue", - "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", - "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", - "backup_complete": "Sauvegarde terminée", - "backup_creating_archive": "Création de l'archive de sauvegarde...", - "backup_delete_error": "Impossible de supprimer « {path:s} »", - "backup_deleted": "La sauvegarde a bien été supprimée", - "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", - "backup_hook_unknown": "Script de sauvegarde « {hook:s} » inconnu", - "backup_invalid_archive": "Archive de sauvegarde incorrecte", - "backup_nothings_done": "Il n'y a rien à sauvegarder", - "backup_output_directory_forbidden": "Dossier de sortie interdit", - "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", - "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", - "backup_running_app_script": "Lancement du script de sauvegarde de l'application « {app:s} »...", - "backup_running_hooks": "Exécution des scripts de sauvegarde...", - "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {app:s}", - "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", - "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer « apt-get remove bind9 && apt-get install dnsmasq »", - "domain_cert_gen_failed": "Impossible de générer le certificat", - "domain_created": "Domaine créé avec succès", - "domain_creation_failed": "Impossible de créer le domaine", - "domain_deleted": "Domaine supprimé avec succès", - "domain_deletion_failed": "Impossible de supprimer le domaine", - "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", - "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", - "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", - "domain_exists": "Le domaine existe déjà", - "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", - "domain_unknown": "Domaine inconnu", - "domain_zone_exists": "Le fichier de zone DNS existe déjà", - "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", - "done": "Terminé.", - "downloading": "Téléchargement...", - "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", - "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", - "dyndns_cron_removed": "La tâche cron pour DynDNS a été enlevée avec succès", - "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", - "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", - "dyndns_registered": "Domaine DynDNS enregistré avec succès", - "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}", - "dyndns_unavailable": "Sous-domaine DynDNS indisponible", - "executing_command": "Exécution de la commande « {command:s} »...", - "executing_script": "Exécution du script « {script:s} »...", - "extracting": "Extraction...", - "field_invalid": "Champ incorrect : « {:s} »", - "firewall_reload_failed": "Impossible de recharger le pare-feu", - "firewall_reloaded": "Pare-feu rechargé avec succès", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", - "format_datetime_short": "%d/%m/%Y %H:%M", - "hook_argument_missing": "Argument manquant : '{:s}'", - "hook_choice_invalid": "Choix incorrect : '{:s}'", - "hook_exec_failed": "Échec de l'exécution du script", - "hook_exec_not_terminated": "L'exécution du script ne s'est pas terminée", - "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", - "hook_name_unknown": "Nom de script « {:s} » inconnu", - "installation_complete": "Installation terminée", - "installation_failed": "Échec de l'installation", - "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes sûrement dans un conteneur, ou alors votre noyau ne le supporte pas.", - "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", - "ldap_initialized": "Répertoire LDAP initialisé avec succès", - "license_undefined": "indéfinie", - "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire « {:s} »", - "mail_domain_unknown": "Domaine « {:s} » de l'adresse mail inconnu", - "mail_forward_remove_failed": "Impossible de supprimer l'adresse courriel de transfert « {:s} »", - "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Domaine principal modifié avec succès", - "monitor_disabled": "Le suivi de l'état du serveur a été désactivé avec succès", - "monitor_enabled": "Suivi de l'état du serveur activé avec succès", - "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", - "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", - "monitor_period_invalid": "Période de temps incorrecte", - "monitor_stats_file_not_found": "Le fichier de statistiques est introuvable", - "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", - "monitor_stats_period_unavailable": "Aucune statistique n'est disponible pour la période", - "mountpoint_unknown": "Point de montage inconnu", - "mysql_db_creation_failed": "Impossible de créer la base de données MySQL", - "mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL", - "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", - "network_check_mx_ko": "L'enregistrement DNS MX n'est pas précisé", - "network_check_smtp_ko": "Le trafic mail sortant (port 25 SMTP) semble bloqué par votre réseau", - "network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n'est pas bloqué", - "new_domain_required": "Vous devez spécifier le nouveau domaine principal", - "no_appslist_found": "Aucune liste d'applications trouvée", - "no_internet_connection": "Le serveur n'est pas connecté à internet", - "no_ipv6_connectivity": "La connectivité IPv6 n'est pas disponible", - "no_restore_script": "Le script de sauvegarde n'a pas été trouvé pour l'application « {app:s} »", - "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", - "not_enough_disk_space": "L'espace disque est insuffisant sur « {path:s} »", - "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", - "packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour ultérieurement", - "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", - "path_removal_failed": "Impossible de supprimer le chemin {:s}", - "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", - "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", - "pattern_email": "Doit être une adresse courriel valide (ex. : someone@domain.org)", - "pattern_firstname": "Doit être un prénom valide", - "pattern_lastname": "Doit être un nom valide", - "pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas", - "pattern_mailbox_quota": "Doit être une taille avec le suffixe b/k/M/G/T ou 0 pour désactiver le quota", - "pattern_password": "Doit être composé d'au moins 3 caractères", - "pattern_port": "Doit être un numéro de port valide (ex. : 0-65535)", - "pattern_port_or_range": "Doit être un numéro de port valide (ex. : 0-65535) ou une gamme de ports (ex. : 100:200)", - "pattern_positive_number": "Doit être un nombre positif", - "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas", - "port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", - "port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", - "port_available": "Le port {port:d} est disponible", - "port_unavailable": "Le port {port:d} n'est pas disponible", - "restore_action_required": "Vous devez préciser ce qui est à restaurer", - "restore_already_installed_app": "Une application est déjà installée avec l'id « {app:s} »", - "restore_app_failed": "Impossible de restaurer l'application « {app:s} »", - "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", - "restore_complete": "Restauration terminée", - "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", - "restore_failed": "Impossible de restaurer le système", - "restore_hook_unavailable": "Le script de restauration « {hook:s} » n'est pas disponible sur votre système", - "restore_nothings_done": "Rien n'a été restauré", - "restore_running_app_script": "Lancement du script de restauration pour l'application « {app:s} »...", - "restore_running_hooks": "Exécution des scripts de restauration...", - "service_add_configuration": "Ajout du fichier de configuration {file:s}", - "service_add_failed": "Impossible d'ajouter le service « {:s} »", - "service_added": "Service ajouté avec succès", - "service_already_started": "Le service « {:s} » est déjà démarré", - "service_already_stopped": "Le service « {:s} » est déjà arrêté", - "service_cmd_exec_failed": "Impossible d'exécuter la commande « {:s} »", - "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier).", - "service_disable_failed": "Impossible de désactiver le service « {:s} »", - "service_disabled": "Service « {:s} » désactivé avec succès", - "service_enable_failed": "Impossible d'activer le service « {:s} »", - "service_enabled": "Service « {:s} » activé avec succès", - "service_no_log": "Aucun journal à afficher pour le service « {:s} »", - "service_remove_failed": "Impossible d'enlever le service « {:s} »", - "service_removed": "Service enlevé avec succès", - "service_start_failed": "Impossible de démarrer le service « {:s} »", - "service_started": "Le service « {:s} » a démarré avec succès", - "service_status_failed": "Impossible de déterminer le statut du service « {:s} »", - "service_stop_failed": "Impossible d'arrêter le service « {:s} »", - "service_stopped": "Service « {:s} » arrêté avec succès", - "service_unknown": "Service « {:s} » inconnu", - "services_configured": "La configuration a été générée avec succès", - "show_diff": "Voici les différences :\n{diff:s}", - "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", - "ssowat_conf_updated": "La configuration persistante de SSOwat a été mise à jour avec succès", - "system_upgraded": "Système mis à jour avec succès", - "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", - "unbackup_app": "L'application « {app:s} » ne sera pas sauvegardée", - "unexpected_error": "Une erreur inattendue est survenue", - "unit_unknown": "Unité « {:s} » inconnue", - "unlimit": "Pas de quota", - "unrestore_app": "L'application « {app:s} » ne sera pas restaurée", - "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", - "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", - "upgrade_complete": "Mise à jour terminée", - "upgrading_packages": "Mise à jour des paquets...", - "upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé", - "upnp_disabled": "UPnP désactivé avec succès", - "upnp_enabled": "UPnP activé avec succès", - "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", - "user_created": "Utilisateur créé avec succès", - "user_creation_failed": "Impossible de créer l'utilisateur", - "user_deleted": "Utilisateur supprimé avec succès", - "user_deletion_failed": "Impossible de supprimer l'utilisateur", - "user_home_creation_failed": "Impossible de créer le dossier personnel de l'utilisateur", - "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", - "user_unknown": "Utilisateur inconnu", - "user_update_failed": "Impossible de modifier l'utilisateur", - "user_updated": "Utilisateur modifié avec succès", - "yunohost_already_installed": "YunoHost est déjà installé", - "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", - "yunohost_configured": "YunoHost configuré avec succès", - "yunohost_installing": "Installation de YunoHost...", + "action_invalid": "Action « {action:s} » incorrecte", + "admin_password": "Mot de passe d'administration", + "admin_password_change_failed": "Impossible de modifier le mot de passe d'administration", + "admin_password_changed": "Mot de passe d'administration modifié avec succès", + "app_already_installed": "{app:s} est déjà installé", + "app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}", + "app_argument_invalid": "Valeur invalide pour le paramètre « {name:s} » : {error:s}", + "app_argument_missing": "Paramètre manquant « {:s} »", + "app_argument_required": "Le paramètre « {name:s} » est requis", + "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", + "app_id_invalid": "Id d'application incorrect", + "app_install_files_invalid": "Fichiers d'installation incorrects", + "app_location_already_used": "Une application est déjà installée à cet emplacement", + "app_location_install_failed": "Impossible d'installer l'application à cet emplacement", + "app_manifest_invalid": "Manifeste d'application incorrect", + "app_no_upgrade": "Aucune application à mettre à jour", + "app_not_correctly_installed": "{app:s} semble être mal installé", + "app_not_installed": "{app:s} n'est pas installé", + "app_not_properly_removed": "{app:s} n'a pas été supprimé correctement", + "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", + "app_removed": "{app:s} supprimé avec succès", + "app_requirements_checking": "Vérification des paquets requis...", + "app_requirements_failed": "Impossible de satisfaire les pré-requis : {error}", + "app_requirements_unmeet": "Les pré-requis ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}", + "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", + "app_unknown": "Application inconnue", + "app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté", + "app_upgrade_failed": "Impossible de mettre à jour {app:s}", + "app_upgraded": "{app:s} mis à jour avec succès", + "appslist_fetched": "Liste d'applications récupérée avec succès", + "appslist_removed": "Liste d'applications supprimée avec succès", + "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", + "appslist_unknown": "Liste d'applications inconnue", + "ask_current_admin_password": "Mot de passe d'administration actuel", + "ask_email": "Adresse courriel", + "ask_firstname": "Prénom", + "ask_lastname": "Nom", + "ask_list_to_remove": "Liste à supprimer", + "ask_main_domain": "Domaine principal", + "ask_new_admin_password": "Nouveau mot de passe d'administration", + "ask_password": "Mot de passe", + "backup_action_required": "Vous devez préciser ce qui est à sauvegarder", + "backup_app_failed": "Impossible de sauvegarder l'application « {app:s} »", + "backup_archive_app_not_found": "L'application « {app:s} » n'a pas été trouvée dans l'archive de la sauvegarde", + "backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde", + "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", + "backup_archive_name_unknown": "L'archive locale de sauvegarde nommée « {name:s} » est inconnue", + "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", + "backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde", + "backup_complete": "Sauvegarde terminée", + "backup_creating_archive": "Création de l'archive de sauvegarde...", + "backup_delete_error": "Impossible de supprimer « {path:s} »", + "backup_deleted": "La sauvegarde a bien été supprimée", + "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", + "backup_hook_unknown": "Script de sauvegarde « {hook:s} » inconnu", + "backup_invalid_archive": "Archive de sauvegarde incorrecte", + "backup_nothings_done": "Il n'y a rien à sauvegarder", + "backup_output_directory_forbidden": "Dossier de sortie interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives.", + "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", + "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", + "backup_running_app_script": "Lancement du script de sauvegarde de l'application « {app:s} »...", + "backup_running_hooks": "Exécution des scripts de sauvegarde...", + "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {app:s}", + "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", + "diagnostic_debian_version_error": "Impossible de déterminer la version de Debian : {error}", + "diagnostic_kernel_version_error": "Impossible de récupérer la version du noyau : {error}", + "diagnostic_monitor_disk_error": "Impossible de superviser les disques : {error}", + "diagnostic_monitor_network_error": "Impossible de superviser le réseau : {error}", + "diagnostic_monitor_system_error": "Impossible de superviser le système : {error}", + "diagnostic_no_apps": "Aucune application installée", + "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer « apt-get remove bind9 && apt-get install dnsmasq »", + "domain_cert_gen_failed": "Impossible de générer le certificat", + "domain_created": "Domaine créé avec succès", + "domain_creation_failed": "Impossible de créer le domaine", + "domain_deleted": "Domaine supprimé avec succès", + "domain_deletion_failed": "Impossible de supprimer le domaine", + "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", + "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", + "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", + "domain_exists": "Le domaine existe déjà", + "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", + "domain_unknown": "Domaine inconnu", + "domain_zone_exists": "Le fichier de zone DNS existe déjà", + "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", + "done": "Terminé.", + "downloading": "Téléchargement...", + "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", + "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", + "dyndns_cron_removed": "La tâche cron pour DynDNS a été enlevée avec succès", + "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", + "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", + "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", + "dyndns_key_not_found": "Clé DNS introuvable pour le domaine", + "dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS", + "dyndns_registered": "Domaine DynDNS enregistré avec succès", + "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error:s}", + "dyndns_unavailable": "Sous-domaine DynDNS indisponible", + "executing_command": "Exécution de la commande « {command:s} »...", + "executing_script": "Exécution du script « {script:s} »...", + "extracting": "Extraction...", + "field_invalid": "Champ incorrect : « {:s} »", + "firewall_reload_failed": "Impossible de recharger le pare-feu", + "firewall_reloaded": "Pare-feu rechargé avec succès", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", + "format_datetime_short": "%d/%m/%Y %H:%M", + "hook_argument_missing": "Argument manquant : '{:s}'", + "hook_choice_invalid": "Choix incorrect : '{:s}'", + "hook_exec_failed": "Échec de l'exécution du script « {path:s} »", + "hook_exec_not_terminated": "L'exécution du script « {path:s} » ne s'est pas terminée", + "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", + "hook_name_unknown": "Nom de script « {name:s} » inconnu", + "installation_complete": "Installation terminée", + "installation_failed": "Échec de l'installation", + "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes sûrement dans un conteneur, ou alors votre noyau ne le supporte pas.", + "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "ldap_initialized": "Répertoire LDAP initialisé avec succès", + "license_undefined": "indéfinie", + "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire « {mail:s} »", + "mail_domain_unknown": "Domaine « {domain:s} » de l'adresse mail inconnu", + "mail_forward_remove_failed": "Impossible de supprimer l'adresse courriel de transfert « {mail:s} »", + "maindomain_change_failed": "Impossible de modifier le domaine principal", + "maindomain_changed": "Domaine principal modifié avec succès", + "monitor_disabled": "Le suivi de l'état du serveur a été désactivé avec succès", + "monitor_enabled": "Suivi de l'état du serveur activé avec succès", + "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", + "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", + "monitor_period_invalid": "Période de temps incorrecte", + "monitor_stats_file_not_found": "Le fichier de statistiques est introuvable", + "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", + "monitor_stats_period_unavailable": "Aucune statistique n'est disponible pour la période", + "mountpoint_unknown": "Point de montage inconnu", + "mysql_db_creation_failed": "Impossible de créer la base de données MySQL", + "mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL", + "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", + "network_check_mx_ko": "L'enregistrement DNS MX n'est pas précisé", + "network_check_smtp_ko": "Le trafic mail sortant (port 25 SMTP) semble bloqué par votre réseau", + "network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n'est pas bloqué", + "new_domain_required": "Vous devez spécifier le nouveau domaine principal", + "no_appslist_found": "Aucune liste d'applications trouvée", + "no_internet_connection": "Le serveur n'est pas connecté à internet", + "no_ipv6_connectivity": "La connectivité IPv6 n'est pas disponible", + "no_restore_script": "Le script de sauvegarde n'a pas été trouvé pour l'application « {app:s} »", + "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", + "not_enough_disk_space": "L'espace disque est insuffisant sur « {path:s} »", + "package_not_installed": "Le paquet « {pkgname} » n'est pas installé", + "package_unexpected_error": "Une erreur inattendue est survenue avec le paquet « {pkgname} »", + "package_unknown": "Paquet « {pkgname} » inconnu", + "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", + "packages_upgrade_critical_later": "Les paquets critiques ({packages:s}) seront mis à jour ultérieurement", + "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", + "path_removal_failed": "Impossible de supprimer le chemin {:s}", + "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", + "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", + "pattern_email": "Doit être une adresse courriel valide (ex. : someone@domain.org)", + "pattern_firstname": "Doit être un prénom valide", + "pattern_lastname": "Doit être un nom valide", + "pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas", + "pattern_mailbox_quota": "Doit être une taille avec le suffixe b/k/M/G/T ou 0 pour désactiver le quota", + "pattern_password": "Doit être composé d'au moins 3 caractères", + "pattern_port": "Doit être un numéro de port valide (ex. : 0-65535)", + "pattern_port_or_range": "Doit être un numéro de port valide (ex. : 0-65535) ou une gamme de ports (ex. : 100:200)", + "pattern_positive_number": "Doit être un nombre positif", + "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas", + "port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}", + "port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}", + "port_available": "Le port {port:d} est disponible", + "port_unavailable": "Le port {port:d} n'est pas disponible", + "restore_action_required": "Vous devez préciser ce qui est à restaurer", + "restore_already_installed_app": "Une application est déjà installée avec l'id « {app:s} »", + "restore_app_failed": "Impossible de restaurer l'application « {app:s} »", + "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", + "restore_complete": "Restauration terminée", + "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", + "restore_failed": "Impossible de restaurer le système", + "restore_hook_unavailable": "Le script de restauration « {hook:s} » n'est pas disponible sur votre système", + "restore_nothings_done": "Rien n'a été restauré", + "restore_running_app_script": "Lancement du script de restauration pour l'application « {app:s} »...", + "restore_running_hooks": "Exécution des scripts de restauration...", + "service_add_configuration": "Ajout du fichier de configuration {file:s}", + "service_add_failed": "Impossible d'ajouter le service « {service:s} »", + "service_added": "Service « {service:s} » ajouté avec succès", + "service_already_started": "Le service « {service:s} » est déjà démarré", + "service_already_stopped": "Le service « {service:s} » est déjà arrêté", + "service_cmd_exec_failed": "Impossible d'exécuter la commande « {command:s} »", + "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier).", + "service_configured": "La configuration du service « {service:s} » a été générée avec succès", + "service_configured_all": "La configuration de tous les services a été générée avec succès", + "service_disable_failed": "Impossible de désactiver le service « {service:s} »", + "service_disabled": "Service « {service:s} » désactivé avec succès", + "service_enable_failed": "Impossible d'activer le service « {service:s} »", + "service_enabled": "Service « {service:s} » activé avec succès", + "service_no_log": "Aucun journal à afficher pour le service « {service:s} »", + "service_remove_failed": "Impossible d'enlever le service « {service:s} »", + "service_removed": "Service « {service:s} » enlevé avec succès", + "service_start_failed": "Impossible de démarrer le service « {service:s} »", + "service_started": "Le service « {service:s} » a démarré avec succès", + "service_status_failed": "Impossible de déterminer le statut du service « {service:s} »", + "service_stop_failed": "Impossible d'arrêter le service « {service:s} »", + "service_stopped": "Service « {service:s} » arrêté avec succès", + "service_unknown": "Service « {service:s} » inconnu", + "services_configured": "La configuration a été générée avec succès", + "show_diff": "Voici les différences :\n{diff:s}", + "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", + "ssowat_conf_updated": "La configuration persistante de SSOwat a été mise à jour avec succès", + "system_upgraded": "Système mis à jour avec succès", + "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", + "unbackup_app": "L'application « {app:s} » ne sera pas sauvegardée", + "unexpected_error": "Une erreur inattendue est survenue", + "unit_unknown": "Unité « {unit:s} » inconnue", + "unlimit": "Pas de quota", + "unrestore_app": "L'application « {app:s} » ne sera pas restaurée", + "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", + "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", + "upgrade_complete": "Mise à jour terminée", + "upgrading_packages": "Mise à jour des paquets...", + "upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé", + "upnp_disabled": "UPnP désactivé avec succès", + "upnp_enabled": "UPnP activé avec succès", + "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", + "user_created": "Utilisateur créé avec succès", + "user_creation_failed": "Impossible de créer l'utilisateur", + "user_deleted": "Utilisateur supprimé avec succès", + "user_deletion_failed": "Impossible de supprimer l'utilisateur", + "user_home_creation_failed": "Impossible de créer le dossier personnel de l'utilisateur", + "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", + "user_unknown": "Utilisateur « {user:s} » inconnu", + "user_update_failed": "Impossible de modifier l'utilisateur", + "user_updated": "Utilisateur modifié avec succès", + "yunohost_already_installed": "YunoHost est déjà installé", + "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", + "yunohost_configured": "YunoHost configuré avec succès", + "yunohost_installing": "Installation de YunoHost...", "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »." -} \ No newline at end of file +} diff --git a/locales/it.json b/locales/it.json index db9898485..14c84e1aa 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,31 +1,31 @@ { - "app_already_installed": "{:s} è già installato", - "app_extraction_failed": "Impossibile estrarre i file di installazione", - "app_not_installed": "{:s} non è installato", - "app_unknown": "Applicazione sconosciuta", - "ask_email": "Indirizzo email", - "ask_password": "Password", - "backup_archive_name_exists": "Il nome dell'archivio del backup esiste già", - "backup_complete": "Backup completo", - "backup_invalid_archive": "Archivio di backup non valido", - "backup_output_directory_not_empty": "Directory di output non è vuota", - "backup_running_app_script": "Esecuzione script di backup dell'applicazione '{:s}'...", - "domain_created": "Dominio creato con successo", - "domain_dyndns_invalid": "Dominio non valido da utilizzare con DynDNS", - "domain_exists": "Dominio esiste già", - "ldap_initialized": "LDAP inizializzato con successo", - "pattern_email": "Deve essere un indirizzo e-mail valido (es someone@domain.org)", - "pattern_mailbox_quota": "Deve essere una dimensione con un suffisso b/k/M/G/T o 0 per disabilitare la quota", - "port_already_opened": "Port {} è già aperto per {:s} connessioni", - "port_unavailable": "Porta {} non è disponibile", - "service_add_failed": "Impossibile aggiungere servizio '{:s}'", - "service_cmd_exec_failed": "Impossibile eseguire il comando '{:s}'", - "service_disabled": "Servizio '{:s}' disattivato con successo", - "service_remove_failed": "Impossibile rimuovere il servizio '{:s}'", - "service_removed": "Servizio rimosso con successo", - "service_stop_failed": "Impossibile arrestare il servizio '{:s}'", - "system_username_exists": "Nome utente esiste già negli utenti del sistema", - "unrestore_app": "Applicazione '{app:s}' non verrà ripristinato", - "upgrading_packages": "Aggiornamento dei pacchetti...", + "app_already_installed": "{app:s} è già installato", + "app_extraction_failed": "Impossibile estrarre i file di installazione", + "app_not_installed": "{app:s} non è installato", + "app_unknown": "Applicazione sconosciuta", + "ask_email": "Indirizzo email", + "ask_password": "Password", + "backup_archive_name_exists": "Il nome dell'archivio del backup esiste già", + "backup_complete": "Backup completo", + "backup_invalid_archive": "Archivio di backup non valido", + "backup_output_directory_not_empty": "Directory di output non è vuota", + "backup_running_app_script": "Esecuzione script di backup dell'applicazione '{app:s}'...", + "domain_created": "Dominio creato con successo", + "domain_dyndns_invalid": "Dominio non valido da utilizzare con DynDNS", + "domain_exists": "Dominio esiste già", + "ldap_initialized": "LDAP inizializzato con successo", + "pattern_email": "Deve essere un indirizzo e-mail valido (es someone@domain.org)", + "pattern_mailbox_quota": "Deve essere una dimensione con un suffisso b/k/M/G/T o 0 per disabilitare la quota", + "port_already_opened": "Port {port:d} è già aperto per {ip_version:s} connessioni", + "port_unavailable": "Porta {port:d} non è disponibile", + "service_add_failed": "Impossibile aggiungere servizio '{service:s}'", + "service_cmd_exec_failed": "Impossibile eseguire il comando '{command:s}'", + "service_disabled": "Servizio '{service:s}' disattivato con successo", + "service_remove_failed": "Impossibile rimuovere il servizio '{service:s}'", + "service_removed": "Servizio rimosso con successo", + "service_stop_failed": "Impossibile arrestare il servizio '{service:s}'", + "system_username_exists": "Nome utente esiste già negli utenti del sistema", + "unrestore_app": "Applicazione '{app:s}' non verrà ripristinato", + "upgrading_packages": "Aggiornamento dei pacchetti...", "user_deleted": "Utente cancellato con successo" -} \ No newline at end of file +} diff --git a/locales/nl.json b/locales/nl.json index 9da7e9e4a..a9be4310d 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,109 +1,109 @@ { - "action_invalid": "Ongeldige actie '{:s}'", - "admin_password": "Administration password", - "admin_password_changed": "Het admin-wachtwoord is gewijzigd", - "app_already_installed": "{:s} is al geïnstalleerd", - "app_argument_invalid": "'{name:s}' bevat geldige waarde: {error:s}", - "app_argument_required": "Het '{name:s}' moet ingevuld worden", - "app_extraction_failed": "Kan installatiebestanden niet uitpakken", - "app_id_invalid": "Ongeldige app-id", - "app_install_files_invalid": "Ongeldige installatiebestanden", - "app_location_already_used": "Er is al een app geïnstalleerd op deze locatie", - "app_location_install_failed": "Kan app niet installeren op deze locatie", - "app_manifest_invalid": "Ongeldig app-manifest", - "app_no_upgrade": "Geen apps op te upgraden", - "app_not_installed": "{:s} is niet geinstalleerd ", - "app_recent_version_required": "{:s} vereist een nieuwere versie van moulinette", - "app_removed": "{:s} succesvol verwijderd", - "app_sources_fetch_failed": "Kan bronbestanden niet ophalen", - "app_unknown": "Onbekende app", - "app_upgrade_failed": "Kan niet alle apps updaten", - "app_upgraded": "{:s} succesvol geüpgrade ", - "appslist_fetched": "App-lijst succesvol aangemaakt.", - "appslist_removed": "App-lijst succesvol verwijderd", - "appslist_unknown": "Onbekende app-lijst", - "ask_current_admin_password": "Huidig administratorwachtwoord", - "ask_email": "Email-adres", - "ask_firstname": "Voornaam", - "ask_lastname": "Achternaam", - "ask_new_admin_password": "Nieuw administratorwachtwoord", - "ask_password": "Wachtwoord", - "backup_archive_name_exists": "Backuparchief bestaat al", - "backup_cleaning_failed": "Kan tijdelijke backup directory niet leeg maken", - "backup_creating_archive": "Backup wordt gestart...", - "backup_invalid_archive": "Ongeldig backup archief", - "backup_output_directory_not_empty": "Doelmap is niet leeg", - "backup_running_app_script": "Backup script voor app '{app:s}' is gestart...", - "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {:s} bij te werken", - "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", - "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cert_gen_failed": "Kan certificaat niet genereren", - "domain_created": "Domein succesvol aangemaakt", - "domain_creation_failed": "Kan domein niet aanmaken", - "domain_deleted": "Domein succesvol verwijderd", - "domain_deletion_failed": "Kan domein niet verwijderen", - "domain_dyndns_already_subscribed": "Dit domein is al geregistreed bij DynDNS", - "domain_dyndns_invalid": "Het domein is ongeldig voor DynDNS", - "domain_dyndns_root_unknown": "Onbekend DynDNS root domein", - "domain_exists": "Domein bestaat al", - "domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijderd.", - "domain_unknown": "Onbekend domein", - "domain_zone_exists": "DNS zone bestand bestaat al", - "domain_zone_not_found": "DNS zone bestand niet gevonden voor domein: {:s}", - "done": "Voltooid.", - "downloading": "Downloaden...", - "dyndns_cron_remove_failed": "De cron-job voor DynDNS kon niet worden verwijderd", - "dyndns_ip_update_failed": "Kan het IP adres niet updaten bij DynDNS", - "dyndns_ip_updated": "IP adres is aangepast bij DynDNS", - "dyndns_key_generating": "DNS sleutel word aangemaakt, wacht een moment...", - "dyndns_unavailable": "DynDNS subdomein is niet beschikbaar", - "executing_script": "Script uitvoeren...", - "extracting": "Uitpakken...", - "installation_complete": "Installatie voltooid", - "installation_failed": "Installatie gefaald", - "ldap_initialized": "LDAP staat klaar voor gebruik", - "license_undefined": "undefined", - "mail_alias_remove_failed": "Kan mail alias niet verwijderen '{:s}'", - "monitor_stats_no_update": "Er zijn geen recente monitoringstatistieken bij te werken", - "mysql_db_creation_failed": "Aanmaken MySQL database gefaald", - "mysql_db_init_failed": "Initialiseren MySQL database gefaald", - "mysql_db_initialized": "MySQL database succesvol geïnitialiseerd", - "network_check_smtp_ko": "Uitgaande mail (SMPT port 25) wordt blijkbaar geblokkeerd door uw het netwerk", - "no_appslist_found": "Geen app-lijsten gevonden", - "no_internet_connection": "Server is niet verbonden met het internet", - "no_ipv6_connectivity": "IPv6-stack is onbeschikbaar", - "path_removal_failed": "Kan pad niet verwijderen {:s}", - "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", - "pattern_listname": "Slechts cijfers, letters en '_' zijn toegelaten", - "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", - "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", - "port_already_closed": "Poort {} is al gesloten voor {:s} verbindingen", - "port_already_opened": "Poort {} is al open voor {:s} verbindingen", - "port_available": "Poort {} is beschikbaar", - "port_unavailable": "Poort {} is niet beschikbaar", - "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", - "restore_hook_unavailable": "De restauration hook '{hook:s}' is niet beschikbaar op dit systeem", - "service_add_failed": "Kan service '{:s}' niet toevoegen", - "service_already_started": "Service '{:s}' draait al", - "service_cmd_exec_failed": "Kan '{:s}' niet uitvoeren", - "service_disabled": "Service '{:s}' is uitgeschakeld", - "service_remove_failed": "Kan service '{:s}' niet verwijderen", - "service_removed": "Service werd verwijderd", - "service_stop_failed": "Kan service '{:s}' niet stoppen", - "service_unknown": "De service '{:s}' bestaat niet", - "show_diff": "Let op de volgende verschillen zijn:\n{diff:s}", - "unexpected_error": "Er is een onbekende fout opgetreden", - "unrestore_app": "App '{app:s}' wordt niet teruggezet", - "updating_apt_cache": "Lijst van beschikbare pakketen wordt bijgewerkt", - "upgrade_complete": "Upgrade voltooid", - "upgrading_packages": "Pakketten worden geüpdate...", - "upnp_dev_not_found": "Geen UPnP apparaten gevonden", - "upnp_disabled": "UPnP successvol uitgeschakeld", - "upnp_enabled": "UPnP succesvol ingeschakeld", - "upnp_port_open_failed": "Kan UPnP poorten niet openen", - "user_deleted": "Gebruiker werd verwijderd", - "user_home_creation_failed": "Kan de map voor deze gebruiker niet aanmaken", - "user_unknown": "Gebruikersnaam is onbekend", - "user_update_failed": "Kan gebruiker niet bijwerken", + "action_invalid": "Ongeldige actie '{action:s}'", + "admin_password": "Administration password", + "admin_password_changed": "Het admin-wachtwoord is gewijzigd", + "app_already_installed": "{app:s} is al geïnstalleerd", + "app_argument_invalid": "'{name:s}' bevat geldige waarde: {error:s}", + "app_argument_required": "Het '{name:s}' moet ingevuld worden", + "app_extraction_failed": "Kan installatiebestanden niet uitpakken", + "app_id_invalid": "Ongeldige app-id", + "app_install_files_invalid": "Ongeldige installatiebestanden", + "app_location_already_used": "Er is al een app geïnstalleerd op deze locatie", + "app_location_install_failed": "Kan app niet installeren op deze locatie", + "app_manifest_invalid": "Ongeldig app-manifest", + "app_no_upgrade": "Geen apps op te upgraden", + "app_not_installed": "{app:s} is niet geinstalleerd", + "app_recent_version_required": "{:s} vereist een nieuwere versie van moulinette", + "app_removed": "{app:s} succesvol verwijderd", + "app_sources_fetch_failed": "Kan bronbestanden niet ophalen", + "app_unknown": "Onbekende app", + "app_upgrade_failed": "Kan niet alle apps updaten", + "app_upgraded": "{app:s} succesvol geüpgrade", + "appslist_fetched": "App-lijst succesvol aangemaakt.", + "appslist_removed": "App-lijst succesvol verwijderd", + "appslist_unknown": "Onbekende app-lijst", + "ask_current_admin_password": "Huidig administratorwachtwoord", + "ask_email": "Email-adres", + "ask_firstname": "Voornaam", + "ask_lastname": "Achternaam", + "ask_new_admin_password": "Nieuw administratorwachtwoord", + "ask_password": "Wachtwoord", + "backup_archive_name_exists": "Backuparchief bestaat al", + "backup_cleaning_failed": "Kan tijdelijke backup directory niet leeg maken", + "backup_creating_archive": "Backup wordt gestart...", + "backup_invalid_archive": "Ongeldig backup archief", + "backup_output_directory_not_empty": "Doelmap is niet leeg", + "backup_running_app_script": "Backup script voor app '{app:s}' is gestart...", + "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {app:s} bij te werken", + "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", + "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "Kan certificaat niet genereren", + "domain_created": "Domein succesvol aangemaakt", + "domain_creation_failed": "Kan domein niet aanmaken", + "domain_deleted": "Domein succesvol verwijderd", + "domain_deletion_failed": "Kan domein niet verwijderen", + "domain_dyndns_already_subscribed": "Dit domein is al geregistreed bij DynDNS", + "domain_dyndns_invalid": "Het domein is ongeldig voor DynDNS", + "domain_dyndns_root_unknown": "Onbekend DynDNS root domein", + "domain_exists": "Domein bestaat al", + "domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijderd.", + "domain_unknown": "Onbekend domein", + "domain_zone_exists": "DNS zone bestand bestaat al", + "domain_zone_not_found": "DNS zone bestand niet gevonden voor domein: {:s}", + "done": "Voltooid.", + "downloading": "Downloaden...", + "dyndns_cron_remove_failed": "De cron-job voor DynDNS kon niet worden verwijderd", + "dyndns_ip_update_failed": "Kan het IP adres niet updaten bij DynDNS", + "dyndns_ip_updated": "IP adres is aangepast bij DynDNS", + "dyndns_key_generating": "DNS sleutel word aangemaakt, wacht een moment...", + "dyndns_unavailable": "DynDNS subdomein is niet beschikbaar", + "executing_script": "Script uitvoeren...", + "extracting": "Uitpakken...", + "installation_complete": "Installatie voltooid", + "installation_failed": "Installatie gefaald", + "ldap_initialized": "LDAP staat klaar voor gebruik", + "license_undefined": "undefined", + "mail_alias_remove_failed": "Kan mail alias niet verwijderen '{mail:s}'", + "monitor_stats_no_update": "Er zijn geen recente monitoringstatistieken bij te werken", + "mysql_db_creation_failed": "Aanmaken MySQL database gefaald", + "mysql_db_init_failed": "Initialiseren MySQL database gefaald", + "mysql_db_initialized": "MySQL database succesvol geïnitialiseerd", + "network_check_smtp_ko": "Uitgaande mail (SMPT port 25) wordt blijkbaar geblokkeerd door uw het netwerk", + "no_appslist_found": "Geen app-lijsten gevonden", + "no_internet_connection": "Server is niet verbonden met het internet", + "no_ipv6_connectivity": "IPv6-stack is onbeschikbaar", + "path_removal_failed": "Kan pad niet verwijderen {:s}", + "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", + "pattern_listname": "Slechts cijfers, letters en '_' zijn toegelaten", + "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", + "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", + "port_already_closed": "Poort {port:d} is al gesloten voor {ip_version:s} verbindingen", + "port_already_opened": "Poort {port:d} is al open voor {ip_version:s} verbindingen", + "port_available": "Poort {port:d} is beschikbaar", + "port_unavailable": "Poort {port:d} is niet beschikbaar", + "restore_app_failed": "De app '{app:s}' kon niet worden terug gezet", + "restore_hook_unavailable": "De restauration hook '{hook:s}' is niet beschikbaar op dit systeem", + "service_add_failed": "Kan service '{service:s}' niet toevoegen", + "service_already_started": "Service '{service:s}' draait al", + "service_cmd_exec_failed": "Kan '{command:s}' niet uitvoeren", + "service_disabled": "Service '{service:s}' is uitgeschakeld", + "service_remove_failed": "Kan service '{service:s}' niet verwijderen", + "service_removed": "Service werd verwijderd", + "service_stop_failed": "Kan service '{service:s}' niet stoppen", + "service_unknown": "De service '{service:s}' bestaat niet", + "show_diff": "Let op de volgende verschillen zijn:\n{diff:s}", + "unexpected_error": "Er is een onbekende fout opgetreden", + "unrestore_app": "App '{app:s}' wordt niet teruggezet", + "updating_apt_cache": "Lijst van beschikbare pakketen wordt bijgewerkt", + "upgrade_complete": "Upgrade voltooid", + "upgrading_packages": "Pakketten worden geüpdate...", + "upnp_dev_not_found": "Geen UPnP apparaten gevonden", + "upnp_disabled": "UPnP successvol uitgeschakeld", + "upnp_enabled": "UPnP succesvol ingeschakeld", + "upnp_port_open_failed": "Kan UPnP poorten niet openen", + "user_deleted": "Gebruiker werd verwijderd", + "user_home_creation_failed": "Kan de map voor deze gebruiker niet aanmaken", + "user_unknown": "Gebruikersnaam is onbekend", + "user_update_failed": "Kan gebruiker niet bijwerken", "yunohost_configured": "YunoHost configuratie is OK" -} \ No newline at end of file +} diff --git a/locales/pt.json b/locales/pt.json index d2fecf2c8..f46cf91ed 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,148 +1,148 @@ { - "action_invalid": "Acção Inválida '{:s}'", - "admin_password": "Senha de administração", - "admin_password_change_failed": "Não foi possível alterar a senha", - "admin_password_changed": "Senha de administração alterada com êxito", - "app_already_installed": "{:s} já está instalada", - "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", - "app_id_invalid": "ID da aplicação invélida", - "app_install_files_invalid": "Ficheiros para instalação corrompidos", - "app_location_already_used": "Já existe uma aplicação instalada neste diretório", - "app_location_install_failed": "Não foi possível instalar a aplicação neste diretório", - "app_manifest_invalid": "Manifesto da aplicação inválido", - "app_no_upgrade": "Não existem aplicações para atualizar", - "app_not_installed": "{:s} não está instalada", - "app_recent_version_required": "{:s} requer uma versão mais recente da moulinette", - "app_removed": "{:s} removida com êxito", - "app_sources_fetch_failed": "Impossível obter os códigos fontes", - "app_unknown": "Aplicação desconhecida", - "app_upgrade_failed": "Unable to upgrade all apps", - "app_upgraded": "{:s} atualizada com êxito", - "appslist_fetched": "Lista de aplicações processada com êxito", - "appslist_removed": "Lista de aplicações removida com êxito", - "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas", - "appslist_unknown": "Lista de aplicaçoes desconhecida", - "ask_current_admin_password": "Senha de administração atual", - "ask_email": "Correio eletrónico", - "ask_firstname": "Primeiro nome", - "ask_lastname": "Último nome", - "ask_list_to_remove": "Lista para remover", - "ask_main_domain": "Domínio principal", - "ask_new_admin_password": "Senha de administração nova", - "ask_password": "Senha", - "backup_complete": "Backup completo", - "backup_creating_archive": "A criar ficheiro de backup...", - "backup_invalid_archive": "Arquivo de backup inválido", - "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", - "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {:s}", - "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", - "domain_cert_gen_failed": "Não foi possível gerar o certificado", - "domain_created": "Domínio criado com êxito", - "domain_creation_failed": "Não foi possível criar o domínio", - "domain_deleted": "Domínio removido com êxito", - "domain_deletion_failed": "Não foi possível eliminar o domínio", - "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", - "domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", - "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", - "domain_exists": "O domínio já existe", - "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", - "domain_unknown": "Domínio desconhecido", - "domain_zone_exists": "Ficheiro para zona DMZ já existe", - "domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}", - "done": "Concluído.", - "downloading": "Transferência em curso...", - "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", - "dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS", - "dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito", - "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", - "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", - "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", - "dyndns_registered": "Dom+inio DynDNS registado com êxito", - "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {:s}", - "dyndns_unavailable": "Subdomínio DynDNS indisponível", - "executing_script": "A executar o script...", - "extracting": "Extração em curso...", - "field_invalid": "Campo inválido '{:s}'", - "firewall_reloaded": "Firewall recarregada com êxito", - "hook_argument_missing": "Argumento em falta '{:s}'", - "hook_choice_invalid": "Escolha inválida '{:s}'", - "installation_complete": "Instalação concluída", - "installation_failed": "A instalação falhou", - "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", - "ldap_initialized": "LDAP inicializada com êxito", - "license_undefined": "indefinido", - "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{:s}'", - "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{:s}'", - "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{:s}'", - "maindomain_change_failed": "Incapaz alterar o domínio raiz", - "maindomain_changed": "Domínio raiz alterado com êxito", - "monitor_disabled": "Monitorização do servidor parada com êxito", - "monitor_enabled": "Monitorização do servidor ativada com êxito", - "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", - "monitor_not_enabled": "A monitorização do servidor não está ativa", - "monitor_period_invalid": "Período de tempo inválido", - "monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", - "monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", - "monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", - "mountpoint_unknown": "Ponto de montagem desconhecido", - "mysql_db_creation_failed": "Criação da base de dados MySQL falhou", - "mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", - "mysql_db_initialized": "Base de dados MySQL iniciada com êxito", - "new_domain_required": "Deve escrever um novo domínio principal", - "no_appslist_found": "Não foi encontrada a lista de aplicações", - "no_internet_connection": "O servidor não está ligado à Internet", - "packages_no_upgrade": "Não existem pacotes para atualizar", - "packages_upgrade_critical_later": "Os pacotes críticos ({:s}) serão atualizados depois", - "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", - "path_removal_failed": "Incapaz remover o caminho {:s}", - "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", - "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", - "pattern_firstname": "Deve ser um primeiro nome válido", - "pattern_lastname": "Deve ser um último nome válido", - "pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões", - "pattern_password": "Deve ter no mínimo 3 caracteres", - "pattern_port": "Deve ser um número de porta válido (entre 0-65535)", - "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "restore_confirm_yunohost_installed": "Quer mesmo restaurar um sistema já instalado? [{answers:s}]", - "service_add_failed": "Incapaz adicionar serviço '{:s}'", - "service_added": "Serviço adicionado com êxito", - "service_already_started": "O serviço '{:s}' já está em execussão", - "service_already_stopped": "O serviço '{:s}' já está parado", - "service_cmd_exec_failed": "Incapaz executar o comando '{:s}'", - "service_disable_failed": "Incapaz desativar o serviço '{:s}'", - "service_disabled": "O serviço '{:s}' foi desativado com êxito", - "service_enable_failed": "Incapaz de ativar o serviço '{:s}'", - "service_enabled": "Serviço '{:s}' ativado com êxito", - "service_no_log": "Não existem registos para mostrar do serviço '{:s}'", - "service_remove_failed": "Incapaz de remover o serviço '{:s}'", - "service_removed": "Serviço eliminado com êxito", - "service_start_failed": "Não foi possível iniciar o serviço '{:s}'", - "service_started": "O serviço '{:s} foi iniciado com êxito", - "service_status_failed": "Incapaz determinar o estado do serviço '{:s}'", - "service_stop_failed": "Incapaz parar o serviço '{:s}", - "service_stopped": "O serviço '{:s}' foi parado com êxito", - "service_unknown": "Serviço desconhecido '{:s}'", - "ssowat_conf_generated": "Configuração SSOwat gerada com êxito", - "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", - "system_upgraded": "Sistema atualizado com êxito", - "system_username_exists": "O utilizador já existe no registo do sistema", - "unexpected_error": "Ocorreu um erro inesperado", - "unit_unknown": "Unidade desconhecida '{:s}'", - "update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", - "updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", - "upgrade_complete": "Atualização completa", - "upgrading_packages": "Atualização de pacotes em curso...", - "user_created": "Utilizador criado com êxito", - "user_creation_failed": "Não foi possível criar o utilizador", - "user_deleted": "Utilizador eliminado com êxito", - "user_deletion_failed": "Incapaz eliminar o utilizador", - "user_info_failed": "Incapaz obter informações sobre o utilizador", - "user_unknown": "Utilizador desconhecido", - "user_update_failed": "Não foi possível atualizar o utilizador", - "user_updated": "Utilizador atualizado com êxito", - "yunohost_already_installed": "AYunoHost já está instalado", - "yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade", - "yunohost_configured": "YunoHost configurada com êxito", - "yunohost_installing": "A instalar a YunoHost...", + "action_invalid": "Acção Inválida '{action:s}'", + "admin_password": "Senha de administração", + "admin_password_change_failed": "Não foi possível alterar a senha", + "admin_password_changed": "Senha de administração alterada com êxito", + "app_already_installed": "{app:s} já está instalada", + "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", + "app_id_invalid": "ID da aplicação invélida", + "app_install_files_invalid": "Ficheiros para instalação corrompidos", + "app_location_already_used": "Já existe uma aplicação instalada neste diretório", + "app_location_install_failed": "Não foi possível instalar a aplicação neste diretório", + "app_manifest_invalid": "Manifesto da aplicação inválido", + "app_no_upgrade": "Não existem aplicações para atualizar", + "app_not_installed": "{app:s} não está instalada", + "app_recent_version_required": "{:s} requer uma versão mais recente da moulinette", + "app_removed": "{app:s} removida com êxito", + "app_sources_fetch_failed": "Impossível obter os códigos fontes", + "app_unknown": "Aplicação desconhecida", + "app_upgrade_failed": "Unable to upgrade all apps", + "app_upgraded": "{app:s} atualizada com êxito", + "appslist_fetched": "Lista de aplicações processada com êxito", + "appslist_removed": "Lista de aplicações removida com êxito", + "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas", + "appslist_unknown": "Lista de aplicaçoes desconhecida", + "ask_current_admin_password": "Senha de administração atual", + "ask_email": "Correio eletrónico", + "ask_firstname": "Primeiro nome", + "ask_lastname": "Último nome", + "ask_list_to_remove": "Lista para remover", + "ask_main_domain": "Domínio principal", + "ask_new_admin_password": "Senha de administração nova", + "ask_password": "Senha", + "backup_complete": "Backup completo", + "backup_creating_archive": "A criar ficheiro de backup...", + "backup_invalid_archive": "Arquivo de backup inválido", + "backup_output_directory_not_empty": "A pasta de destino não se encontra vazia", + "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {app:s}", + "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", + "domain_cert_gen_failed": "Não foi possível gerar o certificado", + "domain_created": "Domínio criado com êxito", + "domain_creation_failed": "Não foi possível criar o domínio", + "domain_deleted": "Domínio removido com êxito", + "domain_deletion_failed": "Não foi possível eliminar o domínio", + "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", + "domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", + "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", + "domain_exists": "O domínio já existe", + "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", + "domain_unknown": "Domínio desconhecido", + "domain_zone_exists": "Ficheiro para zona DMZ já existe", + "domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}", + "done": "Concluído.", + "downloading": "Transferência em curso...", + "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", + "dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS", + "dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito", + "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", + "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", + "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", + "dyndns_registered": "Dom+inio DynDNS registado com êxito", + "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {error:s}", + "dyndns_unavailable": "Subdomínio DynDNS indisponível", + "executing_script": "A executar o script...", + "extracting": "Extração em curso...", + "field_invalid": "Campo inválido '{:s}'", + "firewall_reloaded": "Firewall recarregada com êxito", + "hook_argument_missing": "Argumento em falta '{:s}'", + "hook_choice_invalid": "Escolha inválida '{:s}'", + "installation_complete": "Instalação concluída", + "installation_failed": "A instalação falhou", + "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", + "ldap_initialized": "LDAP inicializada com êxito", + "license_undefined": "indefinido", + "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'", + "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{domain:s}'", + "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'", + "maindomain_change_failed": "Incapaz alterar o domínio raiz", + "maindomain_changed": "Domínio raiz alterado com êxito", + "monitor_disabled": "Monitorização do servidor parada com êxito", + "monitor_enabled": "Monitorização do servidor ativada com êxito", + "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", + "monitor_not_enabled": "A monitorização do servidor não está ativa", + "monitor_period_invalid": "Período de tempo inválido", + "monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", + "monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", + "monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", + "mountpoint_unknown": "Ponto de montagem desconhecido", + "mysql_db_creation_failed": "Criação da base de dados MySQL falhou", + "mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", + "mysql_db_initialized": "Base de dados MySQL iniciada com êxito", + "new_domain_required": "Deve escrever um novo domínio principal", + "no_appslist_found": "Não foi encontrada a lista de aplicações", + "no_internet_connection": "O servidor não está ligado à Internet", + "packages_no_upgrade": "Não existem pacotes para atualizar", + "packages_upgrade_critical_later": "Os pacotes críticos ({packages:s}) serão atualizados depois", + "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", + "path_removal_failed": "Incapaz remover o caminho {:s}", + "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", + "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", + "pattern_firstname": "Deve ser um primeiro nome válido", + "pattern_lastname": "Deve ser um último nome válido", + "pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões", + "pattern_password": "Deve ter no mínimo 3 caracteres", + "pattern_port": "Deve ser um número de porta válido (entre 0-65535)", + "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "restore_confirm_yunohost_installed": "Quer mesmo restaurar um sistema já instalado? [{answers:s}]", + "service_add_failed": "Incapaz adicionar serviço '{service:s}'", + "service_added": "Serviço adicionado com êxito", + "service_already_started": "O serviço '{service:s}' já está em execussão", + "service_already_stopped": "O serviço '{service:s}' já está parado", + "service_cmd_exec_failed": "Incapaz executar o comando '{command:s}'", + "service_disable_failed": "Incapaz desativar o serviço '{service:s}'", + "service_disabled": "O serviço '{service:s}' foi desativado com êxito", + "service_enable_failed": "Incapaz de ativar o serviço '{service:s}'", + "service_enabled": "Serviço '{service:s}' ativado com êxito", + "service_no_log": "Não existem registos para mostrar do serviço '{service:s}'", + "service_remove_failed": "Incapaz de remover o serviço '{service:s}'", + "service_removed": "Serviço eliminado com êxito", + "service_start_failed": "Não foi possível iniciar o serviço '{service:s}'", + "service_started": "O serviço '{service:s}' foi iniciado com êxito", + "service_status_failed": "Incapaz determinar o estado do serviço '{service:s}'", + "service_stop_failed": "Incapaz parar o serviço '{service:s}'", + "service_stopped": "O serviço '{service:s}' foi parado com êxito", + "service_unknown": "Serviço desconhecido '{service:s}'", + "ssowat_conf_generated": "Configuração SSOwat gerada com êxito", + "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", + "system_upgraded": "Sistema atualizado com êxito", + "system_username_exists": "O utilizador já existe no registo do sistema", + "unexpected_error": "Ocorreu um erro inesperado", + "unit_unknown": "Unidade desconhecida '{unit:s}'", + "update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", + "updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", + "upgrade_complete": "Atualização completa", + "upgrading_packages": "Atualização de pacotes em curso...", + "user_created": "Utilizador criado com êxito", + "user_creation_failed": "Não foi possível criar o utilizador", + "user_deleted": "Utilizador eliminado com êxito", + "user_deletion_failed": "Incapaz eliminar o utilizador", + "user_info_failed": "Incapaz obter informações sobre o utilizador", + "user_unknown": "Utilizador desconhecido", + "user_update_failed": "Não foi possível atualizar o utilizador", + "user_updated": "Utilizador atualizado com êxito", + "yunohost_already_installed": "AYunoHost já está instalado", + "yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade", + "yunohost_configured": "YunoHost configurada com êxito", + "yunohost_installing": "A instalar a YunoHost...", "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'." -} \ No newline at end of file +} diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4b18b5461..9a3b56dd6 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1413,12 +1413,24 @@ def _encode_string(value): def _check_manifest_requirements(manifest): """Check if required packages are met from the manifest""" requirements = manifest.get('requirements', dict()) + # FIXME: Deprecate min_version key if 'min_version' in manifest: requirements['yunohost'] = '>> {0}'.format(manifest['min_version']) logger.debug("the manifest key 'min_version' is deprecated, " "use 'requirements' instead.") - if not requirements: + + # Validate multi-instance app + if manifest.get('multi_instance', False): + # Handle backward-incompatible change introduced in yunohost >= 2.3.6 + # See https://dev.yunohost.org/issues/156 + yunohost_req = requirements.get('yunohost', None) + if (not yunohost_req or + not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'): + raise MoulinetteError(errno.EINVAL, '{0}{1}'.format( + m18n.g('colon', m18n.n('app_incompatible')), + m18n.n('app_package_need_update'))) + elif not requirements: return logger.info(m18n.n('app_requirements_checking')) @@ -1429,7 +1441,8 @@ def _check_manifest_requirements(manifest): *requirements.keys(), strict=True, as_dict=True) except packages.PackageException as e: raise MoulinetteError(errno.EINVAL, - m18n.n('app_requirements_failed', err=str(e))) + m18n.n('app_requirements_failed', + error=str(e))) # Iterate over requirements for pkgname, spec in requirements.items(): diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 03ba0d61a..e9d945a28 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -39,7 +39,9 @@ from moulinette.core import MoulinetteError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger -from yunohost.app import app_info, app_ssowatconf, _is_installed, _parse_app_instance_name +from yunohost.app import ( + app_info, app_ssowatconf, _is_installed, _parse_app_instance_name +) from yunohost.hook import ( hook_info, hook_callback, hook_exec, custom_hook_folder ) @@ -384,11 +386,11 @@ def backup_restore(auth, name, hooks=[], ignore_hooks=False, else: # Retrieve the domain from the backup try: - with open("%s/yunohost/current_host" % tmp_dir, 'r') as f: + with open("%s/conf/ynh/current_host" % tmp_dir, 'r') as f: domain = f.readline().rstrip() except IOError: - logger.debug("unable to retrieve domain from " - "'%s/yunohost/current_host'", tmp_dir, exc_info=1) + logger.debug("unable to retrieve current_host from the backup", + exc_info=1) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) logger.debug("executing the post-install...") diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index c078013c1..b41891fb9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -37,6 +37,8 @@ from urllib import urlopen from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from yunohost.service import service_regen_conf + logger = getActionLogger('yunohost.domain') @@ -78,7 +80,6 @@ def domain_add(auth, domain, dyndns=False): dyndns -- Subscribe to DynDNS """ - from yunohost.service import service_regenconf from yunohost.hook import hook_callback attr_dict = { 'objectClass' : ['mailDomain', 'top'] } @@ -157,10 +158,8 @@ def domain_add(auth, domain, dyndns=False): try: with open('/etc/yunohost/installed', 'r') as f: - service_regenconf(service='nginx') - service_regenconf(service='metronome') - service_regenconf(service='dnsmasq') - service_regenconf(service='rmilter') + service_regen_conf(names=[ + 'nginx', 'metronome', 'dnsmasq', 'rmilter']) os.system('yunohost app ssowatconf > /dev/null 2>&1') except IOError: pass except: @@ -183,7 +182,6 @@ def domain_remove(auth, domain, force=False): force -- Force the domain removal """ - from yunohost.service import service_regenconf from yunohost.hook import hook_callback if not force and domain not in domain_list(auth)['domains']: @@ -206,9 +204,7 @@ def domain_remove(auth, domain, force=False): else: raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed')) - service_regenconf(service='nginx') - service_regenconf(service='metronome') - service_regenconf(service='dnsmasq') + service_regen_conf(names=['nginx', 'metronome', 'dnsmasq']) os.system('yunohost app ssowatconf > /dev/null 2>&1') hook_callback('post_domain_remove', args=[domain]) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index acf51f4e2..08000d793 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -176,6 +176,8 @@ def hook_list(action, list_by='name', show_info=False): def _append_folder(d, folder): # Iterate over and add hook from a folder for f in os.listdir(folder + action): + if f[0] == '.' or f[-1] == '~': + continue path = '%s%s/%s' % (folder, action, f) priority, name = _extract_filename_parts(f) _append_hook(d, priority, name, path) @@ -205,14 +207,22 @@ def hook_list(action, list_by='name', show_info=False): return { 'hooks': result } -def hook_callback(action, hooks=[], args=None): +def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, + pre_callback=None, post_callback=None): """ Execute all scripts binded to an action Keyword argument: action -- Action name hooks -- List of hooks names to execute - args -- Ordered list of arguments to pass to the script + args -- Ordered list of arguments to pass to the scripts + no_trace -- Do not print each command that will be executed + chdir -- The directory from where the scripts will be executed + pre_callback -- An object to call before each script execution with + (name, priority, path, args) as arguments and which must return + the arguments to pass to the script + post_callback -- An object to call after each script execution with + (name, priority, path, succeed) as arguments """ result = { 'succeed': {}, 'failed': {} } @@ -252,26 +262,34 @@ def hook_callback(action, hooks=[], args=None): if not hooks_dict: return result - # Format arguments - if args is None: - args = [] - elif not isinstance(args, list): - args = [args] + # Validate callbacks + if not callable(pre_callback): + pre_callback = lambda name, priority, path, args: args + if not callable(post_callback): + post_callback = lambda name, priority, path, succeed: None # Iterate over hooks and execute them for priority in sorted(hooks_dict): for name, info in iter(hooks_dict[priority].items()): state = 'succeed' - filename = '%s-%s' % (priority, name) + path = info['path'] try: - hook_exec(info['path'], args=args, raise_on_error=True) + hook_args = pre_callback(name=name, priority=priority, + path=path, args=args) + hook_exec(path, args=hook_args, chdir=chdir, + no_trace=no_trace, raise_on_error=True) except MoulinetteError as e: - logger.error(str(e)) state = 'failed' + logger.error(str(e)) + post_callback(name=name, priority=priority, path=path, + succeed=False) + else: + post_callback(name=name, priority=priority, path=path, + succeed=True) try: - result[state][name].append(info['path']) + result[state][name].append(path) except KeyError: - result[state][name] = [info['path']] + result[state][name] = [path] return result @@ -282,7 +300,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, Keyword argument: path -- Path of the script to execute - args -- A list of arguments to pass to the script + args -- Ordered list of arguments to pass to the script raise_on_error -- Raise if the script returns a non-zero exit code no_trace -- Do not print each command that will be executed chdir -- The directory from where the script will be executed diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 7443ffab2..8d77a486e 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -30,20 +30,18 @@ import glob import subprocess import errno import shutil -import difflib import hashlib +from difflib import unified_diff from moulinette.core import MoulinetteError -from moulinette.utils import log +from moulinette.utils import log, filesystem -template_dir = os.getenv( - 'YUNOHOST_TEMPLATE_DIR', - '/usr/share/yunohost/templates' -) -conf_backup_dir = os.getenv( - 'YUNOHOST_CONF_BACKUP_DIR', - '/home/yunohost.backup/conffiles' -) +from yunohost.hook import hook_list, hook_callback + + +base_conf_path = '/home/yunohost.conf' +backup_conf_dir = os.path.join(base_conf_path, 'backup') +pending_conf_dir = os.path.join(base_conf_path, 'pending') logger = log.getActionLogger('yunohost.service') @@ -273,26 +271,196 @@ def service_log(name, number=50): return result -def service_regenconf(service=None, force=False): +def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): """ - Regenerate the configuration file(s) for a service and compare the result - with the existing configuration file. - Prints the differences between files if any. + Regenerate the configuration file(s) for a service Keyword argument: - service -- Regenerate configuration for a specfic service - force -- Override the current configuration with the newly generated - one, even if it has been modified + names -- Services name to regenerate configuration of + with_diff -- Show differences in case of configuration changes + force -- Override all manual modifications in configuration files + dry_run -- Show what would have been regenerated + list_pending -- List pending configuration files and exit """ - from yunohost.hook import hook_callback + result = {} - if service is not None: - hook_callback('conf_regen', [service], args=[force]) - logger.success(m18n.n('service_configured', service=service)) - else: - hook_callback('conf_regen', args=[force]) - logger.success(m18n.n('service_configured_all')) + # Return the list of pending conf + if list_pending: + pending_conf = _get_pending_conf(names) + if with_diff: + for service, conf_files in pending_conf.items(): + for system_path, pending_path in conf_files.items(): + pending_conf[service][system_path] = { + 'pending_conf': pending_path, + 'diff': _get_files_diff( + system_path, pending_path, True), + } + return pending_conf + + # Clean pending conf directory + shutil.rmtree(pending_conf_dir, ignore_errors=True) + filesystem.mkdir(pending_conf_dir, 0755, True) + + # Format common hooks arguments + common_args = [1 if force else 0, 1 if dry_run else 0] + + # Execute hooks for pre-regen + pre_args = ['pre',] + common_args + def _pre_call(name, priority, path, args): + # create the pending conf directory for the service + service_pending_path = os.path.join(pending_conf_dir, name) + filesystem.mkdir(service_pending_path, 0755, True, uid='admin') + # return the arguments to pass to the script + return pre_args + [service_pending_path,] + pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) + + # Update the services name + names = pre_result['succeed'].keys() + if not names: + raise MoulinetteError(errno.EIO, + m18n.n('service_regenconf_failed', + services=', '.join(pre_result['failed']))) + + # Set the processing method + _regen = _process_regen_conf if not dry_run else lambda *a, **k: True + + # Iterate over services and process pending conf + for service, conf_files in _get_pending_conf(names).items(): + logger.info(m18n.n( + 'service_regenconf_pending_applying' if not dry_run else \ + 'service_regenconf_dry_pending_applying', + service=service)) + + conf_hashes = _get_conf_hashes(service) + succeed_regen = {} + failed_regen = {} + + for system_path, pending_path in conf_files.items(): + logger.debug("processing pending conf '%s' to system conf '%s'", + pending_path, system_path) + conf_status = None + regenerated = False + + # Get the diff between files + conf_diff = _get_files_diff( + system_path, pending_path, True) if with_diff else None + + # Check if the conf must be removed + to_remove = True if os.path.getsize(pending_path) == 0 else False + + # Retrieve and calculate hashes + current_hash = conf_hashes.get(system_path, None) + system_hash = _calculate_hash(system_path) + new_hash = None if to_remove else _calculate_hash(pending_path) + + # -> system conf does not exists + if not system_hash: + if to_remove: + logger.debug("> system conf is already removed") + os.remove(pending_path) + continue + if not current_hash or force: + if force: + logger.debug("> system conf has been manually removed") + conf_status = 'force-created' + else: + logger.debug("> system conf does not exist yet") + conf_status = 'created' + regenerated = _regen( + system_path, pending_path, save=False) + else: + logger.warning(m18n.n( + 'service_conf_file_manually_removed', + conf=system_path)) + conf_status = 'removed' + # -> system conf is not managed yet + elif not current_hash: + logger.debug("> system conf is not managed yet") + if system_hash == new_hash: + logger.debug("> no changes to system conf has been made") + conf_status = 'managed' + regenerated = True + elif force and to_remove: + regenerated = _regen(system_path) + conf_status = 'force-removed' + elif force: + regenerated = _regen(system_path, pending_path) + conf_status = 'force-updated' + else: + logger.warning(m18n.n('service_conf_file_not_managed', + conf=system_path)) + conf_status = 'unmanaged' + # -> system conf has not been manually modified + elif system_hash == current_hash: + if to_remove: + regenerated = _regen(system_path) + conf_status = 'removed' + elif system_hash != new_hash: + regenerated = _regen(system_path, pending_path) + conf_status = 'updated' + else: + logger.debug("> system conf is already up-to-date") + os.remove(pending_path) + continue + else: + logger.debug("> system conf has been manually modified") + if force: + regenerated = _regen(system_path, pending_path) + conf_status = 'force-updated' + else: + logger.warning(m18n.n( + 'service_conf_file_manually_modified', + conf=system_path)) + conf_status = 'modified' + + # Store the result + conf_result = {'status': conf_status} + if conf_diff is not None: + conf_result['diff'] = conf_diff + if regenerated: + succeed_regen[system_path] = conf_result + conf_hashes[system_path] = new_hash + if os.path.isfile(pending_path): + os.remove(pending_path) + else: + failed_regen[system_path] = conf_result + + # Check for service conf changes + if not succeed_regen and not failed_regen: + logger.info(m18n.n('service_conf_up_to_date', service=service)) + continue + elif not failed_regen: + logger.success(m18n.n( + 'service_conf_updated' if not dry_run else \ + 'service_conf_would_be_updated', + service=service)) + if succeed_regen and not dry_run: + _update_conf_hashes(service, conf_hashes) + + # Append the service results + result[service] = { + 'applied': succeed_regen, + 'pending': failed_regen + } + + # Return in case of dry run + if dry_run: + return result + + # Execute hooks for post-regen + post_args = ['post',] + common_args + def _pre_call(name, priority, path, args): + # append coma-separated applied changes for the service + if name in result and result[name]['applied']: + regen_conf_files = ','.join(result[name]['applied'].keys()) + else: + regen_conf_files = '' + return post_args + [regen_conf_files,] + hook_callback('conf_regen', names, pre_callback=_pre_call) + + return result def _run_service_command(action, service): @@ -380,175 +548,141 @@ def _tail(file, n, offset=None): except IOError: return [] -def _get_diff(string, filename): - """ - Show differences between a string and a file's content +def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): + """Compare two files and return the differences - Keyword argument: - string -- The string - filename -- The file to compare with + Read and compare two files. The differences are returned either as a delta + in unified diff format or a formatted string if as_string is True. The + header can also be removed if skip_header is True. """ - try: - with open(filename, 'r') as f: - file_lines = f.readlines() + contents = [[], []] + for i, path in enumerate((orig_file, new_file)): + try: + with open(path, 'r') as f: + contents[i] = f.readlines() + except IOError: + pass - string = string + '\n' - new_lines = string.splitlines(True) - if file_lines: - while '\n' == file_lines[-1]: - del file_lines[-1] - return difflib.unified_diff(file_lines, new_lines) - except IOError: return [] + # Compare files and format output + diff = unified_diff(contents[0], contents[1]) + if skip_header: + for i in range(2): + try: + next(diff) + except: + break + if as_string: + result = ''.join(line for line in diff) + return result.rstrip() + return diff -def _hash(filename): - """ - Calculate a MD5 hash of a file - - Keyword argument: - filename -- The file to hash - - """ +def _calculate_hash(path): + """Calculate the MD5 hash of a file""" hasher = hashlib.md5() try: - with open(filename, 'rb') as f: - buf = f.read() - hasher.update(buf) - + with open(path, 'rb') as f: + hasher.update(f.read()) return hasher.hexdigest() except IOError: - return 'no hash yet' + return None -def service_saferemove(service, conf_file, force=False): - """ - Check if the specific file has been modified before removing it. - Backup the file in /home/yunohost.backup +def _get_pending_conf(services=[]): + """Get pending configuration for service(s) - Keyword argument: - service -- Service name of the file to delete - conf_file -- The file to write - force -- Force file deletion + Iterate over the pending configuration directory for given service(s) - or + all if empty - and look for files inside. Each file is considered as a + pending configuration file and therefore must be in the same directory + tree than the system file that it replaces. + The result is returned as a dict of services with pending configuration as + key and a dict of `system_conf_path` => `pending_conf_path` as value. """ - deleted = False + result = {} + if not os.path.isdir(pending_conf_dir): + return result + if not services: + services = os.listdir(pending_conf_dir) + for name in services: + service_conf = {} + service_pending_path = os.path.join(pending_conf_dir, name) + path_index = len(service_pending_path) + for root, dirs, files in os.walk(service_pending_path): + for filename in files: + pending_path = os.path.join(root, filename) + service_conf[pending_path[path_index:]] = pending_path + if service_conf: + result[name] = service_conf + return result + + +def _get_conf_hashes(service): + """Get the registered conf hashes for a service""" + try: + return _get_services()[service]['conffiles'] + except: + logger.debug("unable to retrieve conf hashes for %s", + service, exc_info=1) + return {} + + +def _update_conf_hashes(service, hashes): + """Update the registered conf hashes for a service""" + logger.debug("updating conf hashes for '%s' with: %s", + service, hashes) services = _get_services() - - if not os.path.exists(conf_file): - try: - del services[service]['conffiles'][conf_file] - except KeyError: pass - return True - - # Backup existing file - date = time.strftime("%Y%m%d.%H%M%S") - conf_backup_file = conf_backup_dir + conf_file +'-'+ date - process = subprocess.Popen( - ['install', '-D', conf_file, conf_backup_file] - ) - process.wait() - - # Retrieve hashes - if not 'conffiles' in services[service]: - services[service]['conffiles'] = {} - - if conf_file in services[service]['conffiles']: - previous_hash = services[service]['conffiles'][conf_file] - else: - previous_hash = 'no hash yet' - - current_hash = _hash(conf_file) - - # Handle conflicts - if force or previous_hash == current_hash: - os.remove(conf_file) - try: - del services[service]['conffiles'][conf_file] - except KeyError: pass - deleted = True - else: - services[service]['conffiles'][conf_file] = previous_hash - os.remove(conf_backup_file) - if len(previous_hash) == 32 or previous_hash[-32:] != current_hash: - logger.warning(m18n.n('service_configuration_conflict', - file=conf_file)) - + service_conf = services.get(service, {}) + service_conf['conffiles'] = hashes + services[service] = service_conf _save_services(services) - return deleted +def _process_regen_conf(system_conf, new_conf=None, save=True): + """Regenerate a given system configuration file -def service_safecopy(service, new_conf_file, conf_file, force=False): - """ - Check if the specific file has been modified and display differences. - Stores the file hash in the services.yml file - - Keyword argument: - service -- Service name attached to the conf file - new_conf_file -- Path to the desired conf file - conf_file -- Path to the targeted conf file - force -- Force file overriding + Replace a given system configuration file by a new one or delete it if + new_conf is None. A backup of the file - keeping its directory tree - will + be done in the backup conf directory before any operation if save is True. """ - regenerated = False - services = _get_services() - - if not os.path.exists(new_conf_file): - raise MoulinetteError(errno.EIO, m18n.n('no_such_conf_file', file=new_conf_file)) - - with open(new_conf_file, 'r') as f: - new_conf = ''.join(f.readlines()).rstrip() - - # Backup existing file - date = time.strftime("%Y%m%d.%H%M%S") - conf_backup_file = conf_backup_dir + conf_file +'-'+ date - if os.path.exists(conf_file): - process = subprocess.Popen( - ['install', '-D', conf_file, conf_backup_file] - ) - process.wait() - else: - logger.info(m18n.n('service_add_configuration', file=conf_file)) - - # Add the service if it does not exist - if service not in services.keys(): - services[service] = {} - - # Retrieve hashes - if not 'conffiles' in services[service]: - services[service]['conffiles'] = {} - - if conf_file in services[service]['conffiles']: - previous_hash = services[service]['conffiles'][conf_file] - else: - previous_hash = 'no hash yet' - - current_hash = _hash(conf_file) - diff = list(_get_diff(new_conf, conf_file)) - - # Handle conflicts - if force or previous_hash == current_hash: - with open(conf_file, 'w') as f: f.write(new_conf) - new_hash = _hash(conf_file) - if previous_hash != new_hash: - regenerated = True - elif len(diff) == 0: - new_hash = _hash(conf_file) - else: - new_hash = previous_hash - if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): - logger.warning('{0} {1}'.format( - m18n.n('service_configuration_conflict', file=conf_file), - m18n.n('show_diff', diff=''.join(diff)))) - - # Remove the backup file if the configuration has not changed - if new_hash == previous_hash: - try: - os.remove(conf_backup_file) - except OSError: pass - - services[service]['conffiles'][conf_file] = new_hash - _save_services(services) - - return regenerated + if save: + backup_path = os.path.join(backup_conf_dir, '{0}-{1}'.format( + system_conf.lstrip('/'), time.strftime("%Y%m%d.%H%M%S"))) + backup_dir = os.path.dirname(backup_path) + if not os.path.isdir(backup_dir): + filesystem.mkdir(backup_dir, 0755, True) + shutil.copy2(system_conf, backup_path) + logger.info(m18n.n('service_conf_file_backed_up', + conf=system_conf, backup=backup_path)) + try: + if not new_conf: + os.remove(system_conf) + logger.info(m18n.n('service_conf_file_removed', + conf=system_conf)) + else: + system_dir = os.path.dirname(system_conf) + if not os.path.isdir(system_dir): + filesystem.mkdir(system_dir, 0755, True) + shutil.copyfile(new_conf, system_conf) + logger.info(m18n.n('service_conf_file_updated', + conf=system_conf)) + except: + if not new_conf and os.path.exists(system_conf): + logger.warning(m18n.n('service_conf_file_remove_failed', + conf=system_conf), + exc_info=1) + return False + elif new_conf: + try: + copy_succeed = os.path.samefile(system_conf, new_conf) + except: + copy_succeed = False + finally: + if not copy_succeed: + logger.warning(m18n.n('service_conf_file_copy_failed', + conf=system_conf, new=new_conf), + exc_info=1) + return False + return True diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a17128ed0..f78e32363 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -43,7 +43,7 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a from yunohost.domain import domain_add, domain_list, get_public_ip from yunohost.dyndns import dyndns_subscribe from yunohost.firewall import firewall_upnp, firewall_reload -from yunohost.service import service_status, service_regenconf, service_log +from yunohost.service import service_status, service_regen_conf, service_log from yunohost.monitor import monitor_disk, monitor_network, monitor_system from yunohost.utils.packages import ynh_packages_version @@ -152,7 +152,7 @@ def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): try: with open('/etc/yunohost/installed', 'r') as f: - service_regenconf() + service_regen_conf() except IOError: pass logger.success(m18n.n('maindomain_changed')) @@ -170,13 +170,10 @@ def tools_postinstall(domain, password, ignore_dyndns=False): """ dyndns = not ignore_dyndns - try: - with open('/etc/yunohost/installed') as f: pass - except IOError: - logger.info(m18n.n('yunohost_installing')) - else: - raise MoulinetteError(errno.EPERM, m18n.n('yunohost_already_installed')) - + # Do some checks at first + if os.path.isfile('/etc/yunohost/installed'): + raise MoulinetteError(errno.EPERM, + m18n.n('yunohost_already_installed')) if len(domain.split('.')) >= 3 and not ignore_dyndns: try: r = requests.get('https://dyndns.yunohost.org/domains') @@ -187,10 +184,23 @@ def tools_postinstall(domain, password, ignore_dyndns=False): dyndomain = '.'.join(domain.split('.')[1:]) if dyndomain in dyndomains: if requests.get('https://dyndns.yunohost.org/test/%s' % domain).status_code == 200: - dyndns=True + dyndns = True else: raise MoulinetteError(errno.EEXIST, - m18n.n('dyndns_unavailable')) + m18n.n('dyndns_unavailable')) + + logger.info(m18n.n('yunohost_installing')) + + # Instantiate LDAP Authenticator + auth = init_authenticator(('ldap', 'default'), + {'uri': "ldap://localhost:389", + 'base_dn': "dc=yunohost,dc=org", + 'user_rdn': "cn=admin" }) + auth.authenticate('yunohost') + + # Initialize LDAP for YunoHost + # TODO: Improve this part by integrate ldapinit into conf_regen hook + tools_ldapinit(auth) # Create required folders folders_to_create = [ @@ -230,6 +240,7 @@ def tools_postinstall(domain, password, ignore_dyndns=False): os.system('chmod 644 /etc/ssowat/conf.json.persistent') # Create SSL CA + service_regen_conf(['ssl'], force=True) ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' command_list = [ 'echo "01" > %s/serial' % ssl_dir, @@ -247,16 +258,6 @@ def tools_postinstall(domain, password, ignore_dyndns=False): raise MoulinetteError(errno.EPERM, m18n.n('yunohost_ca_creation_failed')) - # Instantiate LDAP Authenticator - auth = init_authenticator(('ldap', 'default'), - { 'uri': "ldap://localhost:389", - 'base_dn': "dc=yunohost,dc=org", - 'user_rdn': "cn=admin" }) - auth.authenticate('yunohost') - - # Initialize YunoHost LDAP base - tools_ldapinit(auth) - # New domain config tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain, dyndns=dyndns) @@ -275,7 +276,7 @@ def tools_postinstall(domain, password, ignore_dyndns=False): os.system('update-rc.d yunohost-firewall enable') os.system('service yunohost-firewall start') - service_regenconf(force=True) + service_regen_conf(force=True) logger.success(m18n.n('yunohost_configured')) @@ -503,4 +504,4 @@ def tools_diagnosis(auth, private=False): # Domains diagnosis['private']['domains'] = domain_list(auth)['domains'] - return diagnosis \ No newline at end of file + return diagnosis diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index edade553f..5be2103e5 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -98,17 +98,22 @@ class Specifier(object): } def __init__(self, spec): - match = self._regex.search(spec) - if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + if isinstance(spec, basestring): + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - self._spec = ( - match.group("relation").strip(), - match.group("version").strip(), - ) + self._spec = ( + match.group("relation").strip(), + match.group("version").strip(), + ) + elif isinstance(spec, self.__class__): + self._spec = spec._spec + else: + return NotImplemented def __repr__(self): - return "".format(str(self)) + return "".format(str(self)) def __str__(self): return "{0}{1}".format(*self._spec) @@ -138,6 +143,12 @@ class Specifier(object): return self._spec != other._spec + def __and__(self, other): + return self.intersection(other) + + def __or__(self, other): + return self.union(other) + def _get_relation(self, op): return getattr(self, "_compare_{0}".format(self._relations[op])) @@ -167,7 +178,79 @@ class Specifier(object): def __contains__(self, item): return self.contains(item) + def intersection(self, other): + """Make the intersection of two specifiers + + Return a new `SpecifierSet` with version specifier(s) common to the + specifier and the other. + + Example: + >>> Specifier('>= 2.2') & '>> 2.2.1' == '>> 2.2.1' + >>> Specifier('>= 2.2') & '<< 2.3' == '>= 2.2, << 2.3' + + """ + if isinstance(other, basestring): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + # store spec parts for easy access + rel1, v1 = self.relation, self.version + rel2, v2 = other.relation, other.version + result = [] + + if other == self: + result = [other] + elif rel1 == '=': + result = [self] if v1 in other else None + elif rel2 == '=': + result = [other] if v2 in self else None + elif v1 == v2: + result = [other if rel1[1] == '=' else self] + elif v2 in self or v1 in other: + is_self_greater = version_compare(v1, v2) > 0 + if rel1[0] == rel2[0]: + if rel1[0] == '>': + result = [self if is_self_greater else other] + else: + result = [other if is_self_greater else self] + else: + result = [self, other] + return SpecifierSet(result if result is not None else '') + + def union(self, other): + """Make the union of two version specifiers + + Return a new `SpecifierSet` with version specifiers from the + specifier and the other. + + Example: + >>> Specifier('>= 2.2') | '<< 2.3' == '>= 2.2, << 2.3' + + """ + if isinstance(other, basestring): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return SpecifierSet([self, other]) + def contains(self, item): + """Check if the specifier contains an other + + Return whether the item is contained in the version specifier. + + Example: + >>> '2.2.1' in Specifier('<< 2.3') + >>> '2.4' not in Specifier('<< 2.3') + + """ return self._get_relation(self.relation)(item, self.version) @@ -181,7 +264,9 @@ class SpecifierSet(object): """ def __init__(self, specifiers): - specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + if isinstance(specifiers, basestring): + specifiers = [s.strip() for s in specifiers.split(",") + if s.strip()] parsed = set() for specifier in specifiers: @@ -190,7 +275,7 @@ class SpecifierSet(object): self._specs = frozenset(parsed) def __repr__(self): - return "".format(str(self)) + return "".format(str(self)) def __str__(self): return ",".join(sorted(str(s) for s in self._specs)) @@ -199,14 +284,10 @@ class SpecifierSet(object): return hash(self._specs) def __and__(self, other): - if isinstance(other, basestring): - other = SpecifierSet(other) - elif not isinstance(other, SpecifierSet): - return NotImplemented + return self.intersection(other) - specifier = SpecifierSet() - specifier._specs = frozenset(self._specs | other._specs) - return specifiers + def __or__(self, other): + return self.union(other) def __eq__(self, other): if isinstance(other, basestring): @@ -237,7 +318,69 @@ class SpecifierSet(object): def __contains__(self, item): return self.contains(item) + def intersection(self, other): + """Make the intersection of two specifiers sets + + Return a new `SpecifierSet` with version specifier(s) common to the + set and the other. + + Example: + >>> SpecifierSet('>= 2.2') & '>> 2.2.1' == '>> 2.2.1' + >>> SpecifierSet('>= 2.2, << 2.4') & '<< 2.3' == '>= 2.2, << 2.3' + >>> SpecifierSet('>= 2.2, << 2.3') & '>= 2.4' == '' + + """ + if isinstance(other, basestring): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifiers = set(self._specs | other._specs) + intersection = [specifiers.pop()] if specifiers else [] + + for specifier in specifiers: + parsed = set() + for spec in intersection: + inter = spec & specifier + if not inter: + parsed.clear() + break + # TODO: validate with other specs in parsed + parsed.update(inter._specs) + intersection = parsed + if not intersection: + break + return SpecifierSet(intersection) + + def union(self, other): + """Make the union of two specifiers sets + + Return a new `SpecifierSet` with version specifiers from the set + and the other. + + Example: + >>> SpecifierSet('>= 2.2') | '<< 2.3' == '>= 2.2, << 2.3' + + """ + if isinstance(other, basestring): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifiers = SpecifierSet([]) + specifiers._specs = frozenset(self._specs | other._specs) + return specifiers + def contains(self, item): + """Check if the set contains a version specifier + + Return whether the item is contained in all version specifiers. + + Example: + >>> '2.2.1' in SpecifierSet('>= 2.2, << 2.3') + >>> '2.4' not in SpecifierSet('>= 2.2, << 2.3') + + """ return all( s.contains(item) for s in self._specs